Только преднамеренная практика может улучшить.
Предыдущее внимание к Vue3 было в основном направлено на чтение исходного кода, а также на получение некоторых PR, и есть еще один, который, как считается, внес небольшой вклад в великое дело Vue.
https://github.com/vuejs/vue-next/pull/1389
Чтобы лучше понять исходный код Vue3, я планирую использовать прогрессивный подход для завершения сокращенной версии фреймворка Vue.
план написания
Приглашаем всех продолжать уделять внимание и сначала составить простой план.
Этот план обязательно изменится😜, иначе как это называется итерацией.
📎 Мок статус
🚀 Простая версия
| Step | 00 | 01 | 02 | 03 | 04 | 05 | 06 | |
|---|---|---|---|---|---|---|---|---|
| реактивная логика | - | 📎 | 🚀 | 🚀 | 🚀 | 🚀 | 🚀 | |
| функция компиляции | - | 📎 | 📎 | 📎 | 🚀 | 🚀 | 🚀 | |
| Parser | - | 📎 | 📎 | 🚀 | 🚀 | 🚀 | ||
| Transformer | - | - | 📎 | 📎 | 🚀 | 🚀 | 🚀 | |
| Generator | - | - | 📎 | 📎 | 🚀 | 🚀 | 🚀 | |
| среда выполнения | - | 📎 | 📎 | 🚀 | 🚀 | 🚀 | 🚀 | |
| Рендерер | - | 📎 | 📎 | 📎 | 🚀 | 🚀 | 🚀 | |
| Основные особенности | Dom Diff | - | - | - | - | - | 🚀 | 🚀 |
| статический подъемник | - | - | - | - | - | - | 🚀 | |
| пользовательский рендерер | - | - | - | - | - | - | 🚀 | |
| - | - | - | - | - | - | 🚀 |
Step00 NoMVVM
Представьте, как бы мы реализовали такую функцию без фреймворка MVVM.
- Создать модель данных
const data = {
message: 'Hello Vue 3!!'
}
- создать вид
<div id='app'>
<input />
<button></button>
</div>
- Создайте функцию рендеринга, которая обновляет данные модели в представлении.
function update() {
// 更新视图
document.querySelector('button').innerHTML = data.message
document.querySelector('input').value = data.message
}
- Выполнить первое обновление данных
// 首次数据渲染
update()
- Кнопка Bind Click Event
- Измените данные в модели: переверните строку
- Повторный рендеринг данных после изменения модели
document.querySelector('button').addEventListener('click', function () {
data.message = data.message.split('').reverse().join('')
update()
})
- Мониторинг изменений ввода
- Изменять данные в модели при изменении элемента данных
- Повторный рендеринг данных после изменения модели
document.querySelector('input').addEventListener('keyup', function () {
data.message = this.value
update()
})
Общая архитектура Step01 — MVVM (фиктивная версия)
Инфраструктура MVVM фактически добавляет слой виртуальной машины между исходным представлением и моделью для выполнения следующих задач. Завершите мониторинг данных и представлений. Давайте сначала напишем Mock-версию на этом шаге. Фактически, он впервые реализует мониторинг фиксированных представлений и моделей данных.
Определение интерфейса
Интерфейс нашего фреймворка MVVM точно такой же, как у Vue3.
Необходимо определить инициализацию
- шаблон просмотра
- модель данных
- Поведение модели. Например, мы хотим, чтобы сообщения модели данных сортировались в обратном порядке при нажатии.
const App = {
// 视图模板
template: `
<input v-model="message"/>
<button @click='click'>{{message}}</button>
`,
// 数据模型
data() {
return {
message: 'Hello Vue 3!!'
}
},
// 行为函数
methods: {
click() {
this.message = this.message.split('').reverse().join('')
}
}
}
const {
createApp
} = Vue
createApp(App).mount('#app')
процедурный скелет
const Vue = {
createApp(config) {
// 编译过程
const compile = (template) => (observed, dom) => {
}
// 生成渲染函数
const render = compile()
// 定义响应函数
let effective
// 数据劫持
observed = new Proxy(config.data(), {
})
return {
// 初始化
mount: function (container) {
}
}
}
}
скомпилировать функцию рендеринга
Функция рендеринга в инфраструктуре 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)
Просмотреть прослушиватель изменений
Изменение вида браузера в основном отражается в мониторинге изменения элемента ввода, поэтому необходимо только прослушивать событие по привязке.
document.querySelector('input').addEventListener('keyup', function () {
data.message = this.value
})
полный код
<html lang="en">
<body>
<div id='app'></div>
<script>
const App = {
// 视图
template: `
<input v-model="message"/>
<button @click='click'>{{message}}</button>
`,
data() {
return {
message: 'Hello Vue 3!!'
}
},
methods: {
click() {
this.message = this.message.split('').reverse().join('')
}
}
}
const Vue = {
createApp(config) {
// 编译过程
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
}
// 生成渲染函数
const render = compile()
// 定义响应函数
let effective
// 数据劫持
observed = new Proxy(config.data(), {
set(target, key, value, receiver) {
const ret = Reflect.set(target, key, value, receiver)
// 触发函数响应
effective()
return ret
},
})
return {
mount: function (container) {
const dom = document.querySelector(container)
effective = () => render(observed, dom)
render(observed, dom)
}
}
}
}
const {
createApp
} = Vue
createApp(App).mount('#app')
</script>
</body>
</html>
OK написал это сегодня и, наконец, завершил первый шаг, хотя большинство из них все еще исправлены, по крайней мере, общая структура была исправлена.
Step02 Процесс компиляции (Mock)
В этой главе мы в основном рассмотрим функцию компиляции.
Функция скомпилированной функции была упомянута выше
// 编译函数
// 输入值为视图模板
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.
https://vue-next-template-explorer.netlify.com/
"
Разборный парсер
Принцип работы парсера на самом деле представляет собой серию обычных совпадений.
Например:
Соответствие атрибутов тега
-
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}}'
}
],
})
Преобразование обработки преобразования
Предыдущий абзац знаний — это абстрактное синтаксическое дерево, а здесь выполняется специальное преобразование для шаблонов Vue3.
Например: vFor, vOn
В Vue три типа также будут разделены на два уровня обработки.
-
основная логика компиляции compile-core
-
AST-Parser
-
Разрешение базового типа v-for , v-on
image-20200713183256931
-
-
compile-dom Логика компиляции для браузеров
-
v-html
-
v-model
-
v-clock
image-20200713183210079
-
const transfer = ast => ({
children: [{
tag: 'input',
props: {
name: 'model',
exp: {
content: 'message'
},
},
},
{
tag: 'button',
props: {
name: 'click',
exp: {
content: 'message'
},
},
children: [{
content: {
content: 'message'
},
}]
}
],
})
GenerateСоздать визуализатор
Генератор фактически генерирует функции рендеринга на основе преобразованного синтаксического дерева 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
}
Лайк и лайк👍👍👍👍👍 Оставайтесь с нами
я буду продолжать обновлять
В этой статье используетсяmdniceнабор текста
Приглашаем всех присоединиться к нам, чтобы учиться и развиваться вместе.
Я буду продвигать последние новости и отличные статьи как можно скорее.
Видео объяснение видео станции BWoohoo. Scale Proportion.com/video/BV1…
О времени выхода
Пожалуйста, обратитесь к официальному расписанию, чтобы узнать конкретное время.Официальное расписание
В настоящее время в бета-версии Vue3 последняя предназначена в основном для решения проблем со стабильностью. То есть не будет много улучшений основного API. Ю Дашен сказал в прямом эфире, что хотя идей много, большие изменения появятся в 3.1 как можно скорее. Так что текущая версия должна сильно отличаться от официальной версии. Кажется, что вероятность выхода Q2 очень высока.