Прогрессивная ручная структура Vue3.0 - более 20 000 слов - постоянно обновляется

Vue.js

Только преднамеренная практика может улучшить.

Предыдущее внимание к 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原理
Принцип МВВМ

Инфраструктура 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, оно будет напечатано.

То есть, если объект будет изменен, он получит ответ.

image-20200713122621925
image-20200713122621925

Конечно, ответ, который нам нужен, — повторно обновить представление и повторно запустить метод рендеринга.

Сначала создайте абстрактную функцию ответа данных

// 定义响应函数
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) => {
   // 渲染过程
 }
}

Проще говоря

  • Ввод: шаблон просмотра

  • вывод: функция рендеринга

Его можно разделить на три небольших шага

Snip20200713_17
Snip20200713_17
  • Разобрать строку шаблона -> абстрактное синтаксическое дерево 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/

image-20200713150630150
image-20200713150630150
"

Анализ функций компиляции

Разборный парсер

Принцип работы парсера на самом деле представляет собой серию обычных совпадений.

Например:

Соответствие атрибутов тега

  • 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]

Подробно об этом будет рассказано при реализации. Вы можете обратиться к статье.

Парсер AST в действии

Итак, для нашего проекта это можно написать так

// <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
      image-20200713183256931
  • compile-dom Логика компиляции для браузеров

    • v-html

    • v-model

    • v-clock

      image-20200713183210079
      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 очень высока.

карта мозга

>>>>>>>️ Ссылка на карту мозга