В нашем последнем посте мы разобрали монтаж этого нехитрого приложения:

<div id="app">
  <h1>Hello, {{ name }}</h1>
</div>
<script>
  const { createApp } = Vue
  createApp({
    data: () => ({ name: 'Rick' })
  }).mount('#app')
</script>

Изучив исходный код, мы обнаружили, что процесс установки состоит из двух этапов:

смонтировать приложение = создать vNode + визуализировать

Мы рассмотрели первый шаг - создание главного компонента приложения как vNode. Сегодня мы проанализируем рендеринг, а именно ту часть, в которой функция рендеринга является производной от шаблона компонента.

Компиляция шаблона

Для рендеринга компонента Vue компилирует его шаблон в функцию рендеринга, которая используется для создания окончательного HTML-кода компонента. Эта компиляция представляет собой трехэтапный процесс. Опуская некоторые детали, функция compile выглядит следующим образом:

function compile(template) {
  const ast = parse(template)
  transform(ast, transformations)
  return generate(ast)
}

Vue сначала анализирует шаблон в AST в процессе, известном как синтаксический анализ шаблона:

синтаксический анализ (шаблон) = AST

Этот AST представляет собой древовидное представление шаблона, который Vue использует для выполнения оптимизации, например, для определения того, какие ветви статичны и, следовательно, не изменятся. Vue может спокойно игнорировать эти статические ветки в процессе обнаружения изменений.

Каждый узел в AST имеет свойство type, которое сообщает Vue о его природе. Вы можете посмотреть в исходном коде все доступные типы узлов. В зависимости от типа узлы имеют разный набор свойств. Вот диаграмма проанализированного AST для нашего простого компонента приложения, только с основными данными в каждом узле:

Затем AST претерпевает некоторые преобразования, подобные тем, которые применяются директивами. Наконец, преобразованный AST используется для генерации кода для функции рендеринга:

generate (AST) = Код функции рендеринга

Этот трехэтапный процесс лучше понять с помощью диаграммы:

Функция рендеринга

При выполнении функция рендеринга создает полное представление дерева виртуальных узлов. Полученное в результате дерево vNode позже используется для исправления DOM каждый раз, когда в компоненте происходит изменение.

функция рендеринга → Дерево vNode компонента

Давайте выполним быстрое упражнение по отладке с помощью нашего простого приложения, чтобы увидеть, как выглядит функция рендеринга для компонента «hello».

Преобразование шаблона в функцию рендеринга

Давайте откроем наше приложение в Chrome и установим точку останова отладки внутри функции finishComponentSetup, где компилируется шаблон компонента:

В этой функции, если компоненту назначен шаблон и атрибутrender отсутствует, функция рендеринга генерируется функцией compile и назначается компоненту. Этот compile ссылается на compileToFunction функцию, определенную внутри пакета vue.

После этого шага компиляции шаблона устанавливается функция render компонента, как мы видим в отладчике Chrome:

Давайте посмотрим, как выглядит эта функция. Chrome дает нам ссылку на скомпилированную функцию, по которой мы можем перейти, щелкнув значение справа от [[FunctionLocation]]: VM349:formatted:4 на рисунке выше.

Вот код функции для визуализации нашего простого компонента приветствия:

Внутри функции верхнего уровня определяется и возвращается функция с именем render. Эта внутренняя функция расширяет цепочку областей разрешения, добавляя к ней контекст this с помощью блока with (строка 5).

Давайте быстро рассмотрим пример использования блока with, поскольку мы, разработчики JS, не используем его очень часто. Блок with включает в себя все, что ему передано:

const person = { name: 'Morty' }
with (person) {
  // Here, we gain access to person
  console.log(`Hi, ${name}`)
}

Обратите внимание, что нам не нужно было указывать person.name, а указывать только name. Это потому, что область разрешения внутри блока была расширена за счет включения объекта person.

Thethis внутри with block привязан к компоненту, который отображает функция. Точнее, он привязан не напрямую к компоненту, а к Прокси-объекту, нацеленному на компонент. Расширяя область разрешения с помощью прокси компонента, мы получаем доступ к его свойствам / данным / методам и т. Д.… что объясняет, почему функция render имеет доступ к name, определенному в компонент (строка 15).

Таким образом, наш шаблон “<h1>Hello, {{ name }}</h1>” был преобразован в функцию рендеринга, выполнение которой генерирует виртуальную модель DOM, которая используется для определения того, какие изменения необходимо внести в реальную модель DOM.

Давайте визуально представим этот этап компиляции шаблона:

Вы можете найти эту compileToFunction функцию внутри пакета vue, в его файле index.ts.

Если вы хотите изучить, как выглядит код функции рендеринга для данного шаблона, у вас есть это замечательное онлайн-приложение для просмотра шаблонов:

Код для обозревателя шаблонов также является частью репозитория Vue. Вы можете найти его в пакете template-explorer. Обратите внимание, что онлайн-версия приложения показывает код, который компилирует Vue2, который немного отличается от кода в Vue 3.

Краткое резюме

Во втором посте, посвященном анализу исходного кода Vue 3, мы рассмотрели процесс компиляции шаблона. Этот процесс, состоящий из трех этапов, отвечает за преобразование шаблона компонента в функцию рендеринга.

Функция рендеринга для компонента генерирует его виртуальную DOM, которая позже используется Vue, чтобы выяснить, какие изменения необходимо применить к HTML-документу.