опубликовано на прошлой неделе【Официальное руководство по Vue3】🎄 4D Notes | Синхронизированное обучающее видео1050 лайков
🔥На этой неделе я смотрел мини-версию Vue3, написанную от руки You Dashen.Примечания следующие, пожалуйста, поправьте меня.
⚡️Подписаться на официальный аккаунт [Front-end big bus] Ответить [mini-vue] Запросить полный код
1. Общий рабочий процесс
- Компилятор компилирует шаблоны представлений в функции рендеринга.
- Модуль ответа данных инициализирует объект данных как реактивный объект данных.
- просмотр рендеринга
- RenderPhase: модуль рендеринга использует функцию рендеринга для создания виртуального дома на основе данных инициализации.
- MountPhase: создать HTML-страницу просмотра с помощью виртуального Dom
- PatchPhase: после изменения модели данных функция рендеринга будет вызываться снова для создания нового виртуального Dom, а затем выполнять Dom Diff для обновления представления Html.
Во-вторых, разделение труда трех модулей
- модуль обработки данных
- переводчик
- функция рендеринга
1. Модули, реагирующие на данные
Предоставляет методы для создания реактивных объектов, которые можно прослушивать для всех изменений данных.
2. Скомпилируйте модуль
Скомпилируйте HTML-шаблон в функцию рендеринга
Этот процесс компиляции может быть выполнен в следующие два раза
- время выполнения браузера (время выполнения)
- Время компиляции пакета проекта Vue (время компиляции)
3. Функция рендеринга
Функция рендеринга отображает представление на странице в течение следующих трех циклов.
- Render Phase
- Mount Phase
- Patch Phase
3. Прототип MVVM (Mock-версия)
Инфраструктура MVVM фактически добавляет слой виртуальной машины между исходным представлением и моделью для выполнения следующих задач. Завершите мониторинг данных и представлений. Давайте сначала напишем Mock-версию на этом шаге. Фактически, он впервые реализует мониторинг фиксированных представлений и моделей данных.
1. Определение интерфейса
Интерфейс нашего фреймворка MVVM точно такой же, как у Vue3.
Необходимо определить инициализацию
- шаблон просмотра
- модель данных
- Поведение модели. Например, мы хотим, чтобы сообщения модели данных сортировались в обратном порядке при нажатии.
const App = {
// 视图
template: `
<input v-model="message"/>
<button @click='click'>{{message}}</button>
`,
setup() {
// 数据劫持
const state = new Proxy(
{
message: "Hello Vue 3!!",
},
{
set(target, key, value, receiver) {
const ret = Reflect.set(target, key, value, receiver);
// 触发函数响应
effective();
return ret;
},
}
);
const click = () => {
state.message = state.message.split("").reverse().join("");
};
return { state, click };
},
};
const { createApp } = Vue;
createApp(App).mount("#app");
2. Каркас программы
Процесс выполнения программы примерно такой:
const Vue = {
createApp(config) {
// 编译过程
const compile = (template) => (content, dom) => {
};
// 生成渲染函数
const render = compile(config.template);
return {
mount: function (container) {
const dom = document.querySelector(container);
// 实现setup函数
const setupResult = config.setup();
// 数据响应更新视图
effective = () => render(setupResult, dom);
render(setupResult, dom);
},
};
},
};
3. Скомпилируйте функцию рендеринга
Функция рендеринга в инфраструктуре MVVM создается путем компиляции шаблона представления.
// 编译函数
// 输入值为视图模板
const compile = (template) => {
//渲染函数
return (observed, dom) => {
// 渲染过程
}
}
Проще говоря, он анализирует шаблон представления и генерирует функцию рендеринга.
Есть примерно три вещи, чтобы сделать
-
Определите, какие значения необходимо отобразить в соответствии с моделью данных
// <button>{{message}}</button> // 将数据渲染到视图 button = document.createElement('button') button.innerText = observed.message dom.appendChild(button)
-
Привязать события модели
// <button @click='click'>{{message}}</button> // 绑定模型事件 button.addEventListener('click', () => { return config.methods.click.apply(observed) })
-
Определите, какие входы требуют двусторонней привязки
// <input v-model="message"/>
// 创建keyup事件监听输入项修改
input.addEventListener('keyup', function () {
observed.message = this.value
})
полный код
const compile = (template) => (observed, dom) => {
// 重新渲染
let input = dom.querySelector('input')
if (!input) {
input = document.createElement('input')
input.setAttribute('value', observed.message)
input.addEventListener('keyup', function () {
observed.message = this.value
})
dom.appendChild(input)
}
let button = dom.querySelector('button')
if (!button) {
console.log('create button')
button = document.createElement('button')
button.addEventListener('click', () => {
return config.methods.click.apply(observed)
})
dom.appendChild(button)
}
button.innerText = observed.message
}
В-четвертых, реализация ответа данных
Vue обычно использует метод захвата данных. Разница заключается в том, использовать ли DefineProperty или Proxy. То есть захватывать ли одно свойство за раз или один объект за раз. Конечно, последний имеет очевидные преимущества перед первым. Это адаптивный принцип Vue3.
Proxy/Reflect был добавлен в спецификацию ES 2015. Proxy может лучше перехватывать поведение объекта, а Reflect может более элегантно манипулировать объектами. Преимущество
- Он настраивается для всего объекта, а не для свойства объекта, поэтому нет необходимости проходить ключи.
- Массивы поддерживаются, а это DefineProperty — нет. Это избавляет от хакерского процесса перегрузки методов массива.
- Второй параметр Proxy может иметь 13 методов перехвата, что богаче, чем Object.defineProperty().
- Proxy, как новый стандарт, привлек внимание и оптимизацию производительности производителей браузеров, в то время как Object.defineProperty() является существующим старым методом.
- Вложение объектов может быть удобно выполнено с помощью рекурсии.
Сказав так много, давайте начнем с небольшого примера
var obj = new Proxy({}, {
get: function (target, key, receiver) {
console.log(`getting ${key}!`);
return Reflect.get(target, key, receiver);
},
set: function (target, key, value, receiver) {
console.log(`setting ${key}!`);
return Reflect.set(target, key, value, receiver);
}
})
obj.abc = 132
Таким образом, если вы измените значение в obj, оно будет напечатано.
То есть, если объект будет изменен, он получит ответ.
Конечно, ответ, который нам нужен, — повторно обновить представление и повторно запустить метод рендеринга.
Сначала создайте абстрактную функцию ответа данных
// 定义响应函数
let effective
observed = new Proxy(config.data(), {
set(target, key, value, receiver) {
const ret = Reflect.set(target, key, value, receiver)
// 触发函数响应
effective()
return ret
},
})
Во время инициализации мы устанавливаем ответное действие для рендеринга представления.
const dom = document.querySelector(container)
// 设置响应动作为渲染视图
effective = () => render(observed, dom)
render(observed, dom)
1. Слушатель для просмотра изменений
Изменение вида браузера в основном отражается в мониторинге изменения элемента ввода, поэтому необходимо только прослушивать событие по привязке.
document.querySelector('input').addEventListener('keyup', function () {
data.message = this.value
})
2. Полный код
<html lang="en">
<body>
<div id="app"></div>
<script>
const Vue = {
createApp(config) {
// 编译过程
const compile = (template) => (content, dom) => {
// 重新渲染
dom.innerText = "";
input = document.createElement("input");
input.addEventListener("keyup", function () {
content.state.message = this.value;
});
input.setAttribute("value", content.state.message);
dom.appendChild(input);
let button = dom.querySelector("button");
button = document.createElement("button");
button.addEventListener("click", () => {
return content.click.apply(content.state);
});
button.innerText = content.state.message;
dom.appendChild(button);
};
// 生成渲染函数
const render = compile(config.template);
return {
mount: function (container) {
const dom = document.querySelector(container);
const setupResult = config.setup();
effective = () => render(setupResult, dom);
render(setupResult, dom);
},
};
},
};
// 定义响应函数
let effective;
const App = {
// 视图
template: `
<input v-model="message"/>
<button @click='click'>{{message}}</button>
`,
setup() {
// 数据劫持
const state = new Proxy(
{
message: "Hello Vue 3!!",
},
{
set(target, key, value, receiver) {
const ret = Reflect.set(target, key, value, receiver);
// 触发函数响应
effective();
return ret;
},
}
);
const click = () => {
state.message = state.message.split("").reverse().join("");
};
return { state, click };
},
};
const { createApp } = Vue;
createApp(App).mount("#app");
</script>
</body>
</html>
5. Посмотреть процесс рендеринга
Dom => virtual DOM => render functions
1. Что такое Dom, объектная модель документа
HTML сопоставляется с рядом узлов в браузере, который нам удобно вызывать.
2. Что такое виртуальный дом
В Доме много узлов, и производительность прямого запроса и обновления Дома низкая.
Способ представления реального DOM с помощью объектов JavaScript. Представляйте фактический Дом с помощью объекта JS.
3. Что такое функция рендеринга
Во Vue мы компилируем шаблон представления (template) в функцию рендеринга (render function), а затем конвертируем его в виртуальный дом.
4. Эффективно обновлять представления через DomDiff
5. Резюме
Возьмите каштан 🌰 Виртуальный Дом и Дом - это как связь между зданием и планом здания.Допустим, вы хотите добавить кухню на 29-й этаж. ❌ Снести весь 29-й этаж и отстроить заново ✅Сначала нарисуйте дизайн, узнайте разницу между старой и новой структурой, а затем стройте
6. Реализуйте функцию рендеринга
Во Vue мы компилируем шаблон представления (template) в функцию рендеринга (render function), а затем конвертируем его в виртуальный дом.
Процесс рендеринга обычно делится на три части:
- RenderPhase: модуль рендеринга использует функцию рендеринга для создания виртуального дома на основе данных инициализации.
- MountPhase: создать HTML-страницу просмотра с помощью виртуального Dom
- PatchPhase: после изменения модели данных функция рендеринга будет вызываться снова для создания нового виртуального Dom, а затем выполнять Dom Diff для обновления представления Html.
mount: function (container) {
const dom = document.querySelector(container);
const setupResult = config.setup();
const render = config.render(setupResult);
let isMounted = false;
let prevSubTree;
watchEffect(() => {
if (!isMounted) {
dom.innerHTML = "";
// mount
isMounted = true;
const subTree = config.render(setupResult);
prevSubTree = subTree;
mountElement(subTree, dom);
} else {
// update
const subTree = config.render(setupResult);
diff(prevSubTree, subTree);
prevSubTree = subTree;
}
});
},
1.Render Phase
Модуль рендеринга использует функцию рендеринга для создания виртуального дома на основе данных инициализации.
render(content) {
return h("div", null, [
h("div", null, String(content.state.message)),
h(
"button",
{
onClick: content.click,
},
"click"
),
]);
},
2. Mount Phase
Используйте виртуальный дом для создания страницы просмотра Html
function mountElement(vnode, container) {
// 渲染成真实的 dom 节点
const el = (vnode.el = createElement(vnode.type));
// 处理 props
if (vnode.props) {
for (const key in vnode.props) {
const val = vnode.props[key];
patchProp(vnode.el, key, null, val);
}
}
// 要处理 children
if (Array.isArray(vnode.children)) {
vnode.children.forEach((v) => {
mountElement(v, el);
});
} else {
insert(createText(vnode.children), el);
}
// 插入到视图内
insert(el, container);
}
3. Patch Phase(Dom diff)
Как только модель данных изменится, функция рендеринга будет вызвана снова для создания нового виртуального Dom, а затем выполните Dom Diff для обновления представления Html.
function patchProp(el, key, prevValue, nextValue) {
// onClick
// 1. 如果前面2个值是 on 的话
// 2. 就认为它是一个事件
// 3. on 后面的就是对应的事件名
if (key.startsWith("on")) {
const eventName = key.slice(2).toLocaleLowerCase();
el.addEventListener(eventName, nextValue);
} else {
if (nextValue === null) {
el.removeAttribute(key, nextValue);
} else {
el.setAttribute(key, nextValue);
}
}
}
Через DomDiff — эффективное обновление представлений
function diff(v1, v2) {
// 1. 如果 tag 都不一样的话,直接替换
// 2. 如果 tag 一样的话
// 1. 要检测 props 哪些有变化
// 2. 要检测 children -》 特别复杂的
const { props: oldProps, children: oldChildren = [] } = v1;
const { props: newProps, children: newChildren = [] } = v2;
if (v1.tag !== v2.tag) {
v1.replaceWith(createElement(v2.tag));
} else {
const el = (v2.el = v1.el);
// 对比 props
// 1. 新的节点不等于老节点的值 -> 直接赋值
// 2. 把老节点里面新节点不存在的 key 都删除掉
if (newProps) {
Object.keys(newProps).forEach((key) => {
if (newProps[key] !== oldProps[key]) {
patchProp(el, key, oldProps[key], newProps[key]);
}
});
// 遍历老节点 -》 新节点里面没有的话,那么都删除掉
Object.keys(oldProps).forEach((key) => {
if (!newProps[key]) {
patchProp(el, key, oldProps[key], null);
}
});
}
// 对比 children
// newChildren -> string
// oldChildren -> string oldChildren -> array
// newChildren -> array
// oldChildren -> string oldChildren -> array
if (typeof newChildren === "string") {
if (typeof oldChildren === "string") {
if (newChildren !== oldChildren) {
setText(el, newChildren);
}
} else if (Array.isArray(oldChildren)) {
// 把之前的元素都替换掉
v1.el.textContent = newChildren;
}
} else if (Array.isArray(newChildren)) {
if (typeof oldChildren === "string") {
// 清空之前的数据
n1.el.innerHTML = "";
// 把所有的 children mount 出来
newChildren.forEach((vnode) => {
mountElement(vnode, el);
});
} else if (Array.isArray(oldChildren)) {
// a, b, c, d, e -> new
// a1,b1,c1,d1 -> old
// 如果 new 的多的话,那么创建一个新的
// a, b, c -> new
// a1,b1,c1,d1 -> old
// 如果 old 的多的话,那么把多的都删除掉
const length = Math.min(newChildren.length, oldChildren.length);
for (let i = 0; i < length; i++) {
const oldVnode = oldChildren[i];
const newVnode = newChildren[i];
// 可以十分复杂
diff(oldVnode, newVnode);
}
if (oldChildren.length > length) {
// 说明老的节点多
// 都删除掉
for (let i = length; i < oldChildren.length; i++) {
remove(oldChildren[i], el);
}
} else if (newChildren.length > length) {
// 说明 new 的节点多
// 那么需要创建对应的节点
for (let i = length; i < newChildren.length; i++) {
mountElement(newChildren[i], el);
}
}
}
}
}
}
Семь, принцип компиляции
Это место не было реализовано You Dashen, и тогда дядя предоставит вам суперкраткую версию В этой главе мы в основном рассмотрим функцию компиляции.
Функция скомпилированной функции была упомянута выше
// 编译函数
// 输入值为视图模板
const compile = (template) => {
//渲染函数
return (observed, dom) => {
// 渲染过程
}
}
Проще говоря
- Ввод: шаблон просмотра
- вывод: функция рендеринга
Его можно разделить на три небольших шага
-
Разобрать строку шаблона -> абстрактное синтаксическое дерево AST (Abstract Syntax Treee)
-
Маркеры преобразования преобразования, такие как v-bind v-if v-для преобразования
-
Генерация AST -> функция рендеринга
// 模板字符串 -> AST(Abstract Syntax Treee)抽象语法树 let ast = parse(template) // 转换处理 譬如 v-bind v-if v-for的转换 ast = transfer(ast) // AST -> 渲染函数 return generator(ast)
Мы можем почувствовать это через онлайн-версию VueTemplateExplorer.
1. Разбирать парсер
Принцип работы парсера на самом деле представляет собой серию обычных совпадений.
Например:
Соответствие атрибутов тега
-
class="title"
-
class='title'
-
class=title
const attr = /([a-zA-Z_:][-a-zA-Z0-9_:.]*)=("([^"]*)"|'([^']*)'|([^\s"'=<>`]+)/
"class=abc".match(attr);
// output
(6) ["class=abc", "class", "abc", undefined, undefined, "abc", index: 0, input: "class=abc", groups: undefined]
"class='abc'".match(attr);
// output
(6) ["class='abc'", "class", "'abc'", undefined, "abc", undefined, index: 0, input: "class='abc'", groups: undefined]
Подробно об этом будет рассказано при реализации. Вы можете обратиться к статье.
Итак, для нашего проекта это можно написать так
// <input v-model="message"/>
// <button @click='click'>{{message}}</button>
// 转换后的AST语法树
const parse = template => ({
children: [{
tag: 'input',
props: {
name: 'v-model',
exp: {
content: 'message'
},
},
},
{
tag: 'button',
props: {
name: '@click',
exp: {
content: 'message'
},
},
content:'{{message}}'
}
],
})
2. Преобразование обработки преобразования
Предыдущий абзац знаний — это абстрактное синтаксическое дерево, а здесь выполняется специальное преобразование для шаблонов Vue3.
Например: vFor, vOn
В Vue три типа также будут разделены на два уровня обработки.
-
основная логика компиляции compile-core
-
AST-Parser
-
Разрешение базового типа v-for , v-on
-
-
compile-dom Логика компиляции для браузеров
-
v-html
-
v-model
-
v-clock
-
const transfer = ast => ({
children: [{
tag: 'input',
props: {
name: 'model',
exp: {
content: 'message'
},
},
},
{
tag: 'button',
props: {
name: 'click',
exp: {
content: 'message'
},
},
children: [{
content: {
content: 'message'
},
}]
}
],
})
3. Сгенерируйте визуализатор
Генератор фактически генерирует функции рендеринга на основе преобразованного синтаксического дерева AST. Конечно, вы можете отображать разные результаты для одного и того же синтаксического дерева. Например, если вы хотите, чтобы кнопка отображалась как кнопка или блок svg, это зависит от ваших предпочтений. Это называется пользовательским рендерером. Здесь мы просто пишем фиксированный плейсхолдер Dom renderer. Я расширяю обработку, когда она будет реализована позже.
const generator = ast => (observed, dom) => {
// 重新渲染
let input = dom.querySelector('input')
if (!input) {
input = document.createElement('input')
input.setAttribute('value', observed.message)
input.addEventListener('keyup', function () {
observed.message = this.value
})
dom.appendChild(input)
}
let button = dom.querySelector('button')
if (!button) {
console.log('create button')
button = document.createElement('button')
button.addEventListener('click', () => {
return config.methods.click.apply(observed)
})
dom.appendChild(button)
}
button.innerText = observed.message
}
🔥Подпишитесь на официальный аккаунт [front-end big bus] Ответьте на [mini-vue], чтобы запросить полный код
Подписаться на Full Stack Uncle Ran
Последние статьи (Спасибо за вашу поддержку и поддержку 🌹🌹🌹)
- 🔥 【Официальное руководство Vue3】🎄 4D Notes | Синхронизированное обучающее видео1050 лайков
- 🔥 39-летний мужчина спешит на дорогу | Ежегодный очерк Nuggets100 лайков
- 🔥 Element3 Development Insider — разработка плагина Vue CLI167 лайков
- 🔥 Каждый день делает серию колес500+ лайков
- 🔥 Итоги глобальной конференции по галантерее Vue3.0267 лайков
Добро пожаловать в Paizhuan, давайте вместе обсудим более элегантную реализацию