предисловие
Как упоминалось ранее в Vue3mountДелай внутри дела, где первый шагmountКак объяснялось ранее, эта статья начнется с функции монтирования файла apiCreateApp в папке runtime-core на втором этапе, а остальные шаги будут объяснены по очереди. Интерпретируйте код ядра в соответствии с основным процессом, скачок может быть относительно большим, но какой файл будет указан в процессе объяснения.
текст
Здесь начинается текст, здесь начинается крепление. Давайте сначала взглянем на исходный код. Исходный код упомянутой ранее части dom здесь упоминаться не будет. Начнем с основной части:
packages/runtime-core/src/apiCreateApp.ts
mount(rootContainer: HostElement, isHydrate?: boolean): any {
if (!isMounted) {
const vnode = createVNode(
rootComponent as ConcreteComponent,
rootProps
)
vnode.appContext = context
if (isHydrate && hydrate) {
hydrate(vnode as VNode<Node, Element>, rootContainer as any)
} else {
render(vnode, rootContainer)
}
isMounted = true
app._container = rootContainer
;(rootContainer as any).__vue_app__ = app
return vnode.component!.proxy
}
}
Исходный код здесь на самом деле довольно прост.
- Вызовите createVNode, чтобы получить vnode, rootComponent — это данные конфигурации, передаваемые при вызове createApp(config), rootProps — это реквизиты root, которые будут проверены, как упоминалось ранее, обычно во время использования, rootProps равен нулю;
- Сохраните контекст на узле-последователе;
- Вызвать функцию рендеринга, здесь объясняется только рендеринг;
- isMounted имеет значение true;
- _container экземпляра сохраняется как текущий rootContainer;
- rootContainer добавляет атрибут __vue_app__ и устанавливает его как текущий экземпляр приложения;
- Возвращает прокси для vnode.component.
Основной код рендеринга — это функция рендеринга.
render
Роль функции рендеринга в Vue2 и Vue3 совершенно разная.
- Функция рендеринга в Vue2 выполняет определенную работу и представляет собой настоящую операцию рендеринга.Возвращаемый результат — vnode, который можно просмотреть здесь.Интерпретация исходного кода Vue2 (семь) — крепление;
- Функция рендеринга в Vue3 предназначена для распространения, что эквивалентно маршрутизатору, две строки, размонтировать и исправить, и никаких результатов не возвращается.
Взгляните на исходный код рендера:
packages/runtime-core/src/renderer.ts
const render: RootRenderFunction = (vnode, container) => {
if (vnode == null) {
if (container._vnode) {
unmount(container._vnode, null, null, true)
}
} else {
patch(container._vnode || null, vnode, container)
}
flushPostFlushCbs()
container._vnode = vnode
}
Приведенный выше код является исходным кодом функции рендеринга:
- Параметр 1: vnode — обновляемый vnode на странице, полученный с помощью createVNode выше, container — отображаемый контейнер;
- Сначала оценивается vnode, если он пустой, а container._vnode имеет значение, то есть есть предыдущая отрисовка DOM, выполняется операция размонтирования;
- Если vnode не пуст, выполните операцию исправления, dom diff и рендеринг;
- Выполните функцию flushPostFlushCbs, вызовите планировщик и реализуйте его с помощью Promise. Отличие от Vue2 в том, что Vue2 обрабатывается макро-задачами или микро-задачами.
- Сохраните _vnode контейнера в качестве текущего vnode, чтобы упростить операцию сравнения dom позже, которая аналогична Vue2.
Поскольку это рендеринг, vnode не будет пустым, и он обязательно перейдет в часть функции исправления.Давайте посмотрим на код в части исправления:
packages/runtime-core/src/renderer.ts
const patch: PatchFn = (
n1, // old
n2, // new
container, // 容器
anchor = null,
parentComponent = null,
parentSuspense = null,
isSVG = false,
optimized = false
) => {
// 如果type不相同,则把n1直接卸载掉
if (n1 && !isSameVNodeType(n1, n2)) {
anchor = getNextHostNode(n1)
unmount(n1, parentComponent, parentSuspense, true)
n1 = null
}
if (n2.patchFlag === PatchFlags.BAIL) {
optimized = false
n2.dynamicChildren = null
}
const {type, ref, shapeFlag} = n2
switch (type) {
case Text:
processText(n1, n2, container, anchor)
break
case Comment:
processCommentNode(n1, n2, container, anchor)
break
case Static:
if (n1 == null) {
mountStaticNode(n2, container, anchor, isSVG)
} else if (__DEV__) {
patchStaticNode(n1, n2, container, isSVG)
}
break
case Fragment:
processFragment(
n1,
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
)
break
default:
if (shapeFlag & ShapeFlags.ELEMENT) {
processElement(
n1,
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
)
} else if (shapeFlag & ShapeFlags.COMPONENT) {
processComponent(
n1,
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
)
} else if (shapeFlag & ShapeFlags.TELEPORT) {
;(type as typeof TeleportImpl).process(
n1 as TeleportVNode,
n2 as TeleportVNode,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized,
internals
)
} else if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
;(type as typeof SuspenseImpl).process(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized, internals)
} else if (__DEV__) {
warn('Invalid VNode type:', type, `(${typeof type})`)
}
}
if (ref != null && parentComponent) {
setRef(ref, n1 && n1.ref, parentComponent, parentSuspense, n2)
}
}
Функция патча от двери до двери была прочесана и проанализирована для входа, и был получен следующий рисунок, в котором есть несколько часто используемых линий:
- processFragment: функция для обработки фрагментов (массивов dom);
- processElement: функция, которая обрабатывает элемент;
- ProcessComponent: Компоненты обработки;
Далее мы изучим пример, в котором будут задействованы processFragment и processElement, и выполним операцию diff diff;
пример рендеринга
Теперь мы рассмотрим пример от начала до конца и шаг за шагом пройдемся по используемым функциям от начала до конца. Предположим, теперь у нас есть список:
packages/vue/examples/classic/hello.js
const app = Vue.createApp({
data() {
return {
list: ['a', 'b', 'c', 'd']
}
}
});
app.mount('#demo')
packages/vue/examples/classic/hello.html
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="initial-scale=1.0,maximum-scale=1.0,minimum-scale=1.0,
user-scalable=no,target-densitydpi=medium-dpi,viewport-fit=cover"/>
<title>Vue3.js hello example</title>
<script src="../../dist/vue.global.js"></script>
</head>
<body>
<div id="demo">
<ul>
<li v-for="item in list" :key="item">
{{item}}
</li>
</ul>
</div>
<script src="./hello.js"></script>
</body>
</html>
Мы создаем два новых файла, hello.js и hello.html, в корневом каталоге исходного кода Vue3, в соответствующем каталоге, копируем приведенный выше код в объектный файл, а затем запускаем его в корневом каталоге.npm run dev, ну вот сейчас проект запущен. Затем откройте браузер и введите URL-адрес:
файл:///Users/draven/mywork/vue-3.0.0/packages/vue/examples/classic/hello.html
; вы можете увидеть рендеринг страницы:
На данный момент это успешно. Далее мы изучим, как работает эффект на странице, начиная с функции рендеринга выше.
- 1. Начать работу: позвонить
render(vnode, rootContainer), функция работает вpackages/runtime-core/src/apiCreateApp.ts, объявление функции рендеринга находится вpackages/runtime-core/src/renderer.ts;Параметр vnode генерируется вызовом createVNode выше, а параметр rootContainer — это элемент с идентификатором demo, переданным выше; - 2. Следующая запись
packages/runtime-core/src/renderer.tsфайл, большинство следующих функций находятся в этом файле, если есть особые обстоятельства, это будет объяснено. - 3. Следующий запуск: вызов внутри функции рендеринга
patch(container._vnode || null, vnode, container) -
- 3.1 Первый параметр — это старый vnode, потому что это первая визуализация, старый vnode не существует, поэтому он равен нулю; второй параметр — прозрачно переданный vnode; третий параметр — прозрачно переданный контейнер (#demo ) ;
-
- 3.2 Функция patch принимает и другие параметры, но мы пока не можем ее использовать:
patch(n1, n2, container, anchor = null, parentComponent = null, parentSuspense = null, isSVG = false, optimized = false);n1 имеет значение null, n2 – обновляемый vnode, а контейнер – прозрачный transfer#demo;
- 3.2 Функция patch принимает и другие параметры, но мы пока не можем ее использовать:
-
- 3.3 В это время n1 равно null, а n2 все еще является объектом:
Суждение в это время будет соответствовать
shapeFlag & ShapeFlags.COMPONENT, идти кprocessComponent(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized)функция; в настоящее время значение параметра: n1 равно null, n2 показано на рисунке, а контейнер — #demo;
- 4. Функция processComponent будет судить n1.Если n1 не равно нулю, это оказывается операцией обновления и вызовом updateComponent, в это время мы делаем рендеринг в первый раз, поэтому мы не будем проходить операцию обновления и пройдем другая логика; если это тип компонента keepAlive, используйте логику активации; на данный момент мы не за компонент keepalive, поэтому используйте функцию mountComponent,
mountComponent(n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized), параметр n2 — это изображение выше, контейнер — #demo, а другие параметры по-прежнему имеют значение null (false) по умолчанию; - 5. Функция mountComponent сначала вызовет createComponentInstance для создания экземпляра текущего n2, затем вызовет setupComponent для инициализации свойств и слотов и т. д. и, наконец, вызовет
setupRenderEffect(instance, initialVNode, container, anchor, parentSuspense, isSVG, optimized ), экземпляр параметра — это экземпляр, сгенерированный выше, initialVNode по-прежнему имеет значение n2 на приведенном выше рисунке, контейнер — это #demo, а остальные — значения по умолчанию; - 6. Функция setupRenderEffect — очень важная функция. Эта функция монтирует метод обновления для текущего экземпляра. Метод обновления генерируется эффектом. Эффект эффекта в Vue3 эквивалентен наблюдаемому в Vue2; после обновления генерируется, перед монтированием сначала будет запущен сгенерированный метод эффекта, и, наконец, текущий метод эффекта будет возвращен для обновления; запуск функции эффекта эквивалентенЗвонки Watcher попадают в Vue2Process.effect принимает два параметра, первый параметр — это функция componentEffect, которая должна отслеживать изменения и вызывать эту функцию, как упоминалось выше, сначала запустите сгенерированный метод эффекта, а функция componentEffect будет вызываться внутри сгенерированного метода эффекта;
- 7. В функции componentEffect есть две логики, чтобы определить, отрендерился ли он: instance.isMounted, если отрендерился, то переходим к логике обновления, если еще не отрендерили, переходим к неотрендеренному, возьмем посмотрите на исходный код этой части.
function componentEffect() { if (!instance.isMounted) { let vnodeHook: VNodeHook | null | undefined const {el, props} = initialVNode const {bm, m, parent} = instance // beforeMount hook if (bm) { invokeArrayFns(bm) } // onVnodeBeforeMount if ((vnodeHook = props && props.onVnodeBeforeMount)) { invokeVNodeHook(vnodeHook, parent, initialVNode) } const subTree = (instance.subTree = renderComponentRoot(instance)) if (el && hydrateNode) { hydrateNode( initialVNode.el as Node, subTree, instance, parentSuspense ) } else { patch( null, subTree, container, anchor, instance, parentSuspense, isSVG ) initialVNode.el = subTree.el } if (m) { queuePostRenderEffect(m, parentSuspense) } if ((vnodeHook = props && props.onVnodeMounted)) { queuePostRenderEffect(() => { invokeVNodeHook(vnodeHook!, parent, initialVNode) }, parentSuspense) } const {a} = instance if ( a && initialVNode.shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE ) { queuePostRenderEffect(a, parentSuspense) } instance.isMounted = true } else { // no first render } }
Выше показан первый рендеринг после завершения.componentEffectисходный код функции;
-
- 7.1 Сначала вызывается хук-функция beforeMount текущего экземпляра;
-
- 7.2. Вызвать функцию ловушки BeforeMount родительского класса n2;
-
- 7.3 Вызвать функцию renderComponentRoot для рендеринга корневого элемента компонента;
-
- 7.4. Исправление вызова:
patch(null, subTree, container, anchor, instance, parentSuspense, isSVG); В настоящее время значение поддерева:;container — #demo; привязка — null, instance — текущий экземпляр, parentSuspense — null, isSVG — false;
- 7.4. Исправление вызова:
-
- 7.5. Вызвать встроенную функцию ловушки текущего экземпляра; вызвать встроенную функцию ловушки родительского класса n2; вызвать активированную функцию ловушки текущего экземпляра; вместо прямого вызоваВместо этого он вызывается путем помещения его в очередь через queuePostRenderEffect;
-
- 7.6 Наконец, установите isMounted экземпляра в true;
- 8. ВышеВызов patch в функции componentEffect — это начало официального рендеринга., большинство предыдущих эквивалентны сопоставлению данных:
-
- 8.1. Передайте рабочие параметры функции componentEffect, указанной выше, функции patch:
patch(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized); В настоящее время n1 имеет значение null, n2 — это изображение (поддерево) выше, контейнер все еще #demo, привязка имеет значение null, parentComponent — это экземпляр выше, parentSuspense — null, isSVG — false, optimized — false;
- 8.1. Передайте рабочие параметры функции componentEffect, указанной выше, функции patch:
-
- 8.2 Код выполняется последовательно Как видно из рисунка выше, тип поддерева экземпляра, полученного компонентом, — Fragment, и он попадет в функцию processFragment;
-
- 8.3. Параметры, принимаемые processFragment, такие же, как и у функции patch, или приведенные выше значения, без изменений. Давайте посмотрим на исходный код:
По параметрам можно узнать, что будет достигнута текущая логика if, и скелет будет вставлен первым, затем выполнитьconst processFragment = ( n1: VNode | null, n2: VNode, container: RendererElement, anchor: RendererNode | null, parentComponent: ComponentInternalInstance | null, parentSuspense: SuspenseBoundary | null, isSVG: boolean, optimized: boolean ) => { const fragmentStartAnchor = (n2.el = n1 ? n1.el : hostCreateText(''))! const fragmentEndAnchor = (n2.anchor = n1 ? n1.anchor : hostCreateText(''))! let {patchFlag, dynamicChildren} = n2 if (patchFlag > 0) { optimized = true } if (n1 == null) { hostInsert(fragmentStartAnchor, container, anchor) hostInsert(fragmentEndAnchor, container, anchor) mountChildren( n2.children as VNodeArrayChildren, container, fragmentEndAnchor, parentComponent, parentSuspense, isSVG, optimized ) } else { // 其他逻辑 } }mountChildren, n2.children может узнать через поддерево выше, что значение является массивом, и в массиве есть 1 элемент, который является ul, который мы хотим отобразить;
Вы можете видеть, что n2.children будет пройден, а n2.children имеет только один элемент, который является ulconst mountChildren: MountChildrenFn = ( children, container, anchor, parentComponent, parentSuspense, isSVG, optimized, start = 0 ) => { for (let i = start; i < children.length; i++) { const child = (children[i] = optimized ? cloneIfMounted(children[i] as VNode) : normalizeVNode(children[i])) patch( null, child, container, anchor, parentComponent, parentSuspense, isSVG, optimized ) } }
- 8.3. Параметры, принимаемые processFragment, такие же, как и у функции patch, или приведенные выше значения, без изменений. Давайте посмотрим на исходный код:
-
- 8.4 Используя приведенные выше параметры среды выполнения, вызовите
patch(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG = false, optimized);параметры: n1 равно null; дочерний элемент – указанный выше ul; container – #demo, привязка – fragmentEndAnchor в приведенной выше функции processFragment; parentComponent – экземпляр экземпляра; parentSuspense – null; isSVG – false; optimized – истина, потому что в приведенном выше в нем были внесены изменения processFragment;
- 8.4 Используя приведенные выше параметры среды выполнения, вызовите
-
- 8.5.Из приведенных выше параметров видно, что тип ul - ul.В это время он пойдет в функцию processElement.Параметры функции processElement такие же, как и у функции patch.Осуществляется прозрачная передача , Посмотрите на исходный код:
По параметру n1 равно null, можно знать, что он пойдет по логике mountElement, и параметр не изменится. В процессе выполнения mountElement будут обнаружены дочерние элементы ul, и при наличии значения под дочерними элементами ul будет вызвана функция mountChildren:const processElement = ( n1: VNode | null, n2: VNode, container: RendererElement, anchor: RendererNode | null, parentComponent: ComponentInternalInstance | null, parentSuspense: SuspenseBoundary | null, isSVG: boolean, optimized: boolean ) => { isSVG = isSVG || (n2.type as string) === 'svg' if (n1 == null) { mountElement( n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized ) } else { patchElement(n1, n2, parentComponent, parentSuspense, isSVG, optimized) } }
На данный момент vnode.children — это массив, состоящий из 4 li, el — это ul, якорь — null, parentComponent — экземпляр экземпляра, parentSuspense — null, isSVG — false, optimized — true, повторите вышеописанную функцию mountChildren, а затем вернитесь к ней.mountChildren( vnode.children as VNodeArrayChildren, el, null, parentComponent, parentSuspense, isSVG && type !== 'foreignObject', optimized || !!vnode.dynamicChildren )
- 8.5.Из приведенных выше параметров видно, что тип ul - ul.В это время он пойдет в функцию processElement.Параметры функции processElement такие же, как и у функции patch.Осуществляется прозрачная передача , Посмотрите на исходный код:
-
- 8.6 Когда цикл for выполняется в функции mountChildren, тип li — li, он продолжит обрабатывать элемент, повторит вышеуказанные шаги и последовательно завершит выполнение;
- 9. Все вышеперечисленные шаги выполнены, и теперь данные отображаются на странице.
- 10. В это время в основном все делается, что равносильно бездействию основной очереди, вызов
flushPostFlushCbs()Начать выполнение функции в очереди; - 11. Наконец, укажите атрибут _vnode контейнера на текущий vnode, это удобно для dom diff в следующий раз.
- 12. Первый запуск рендеринга завершен.
Эпилог
Эта глава посвящена процессу рендеринга первого рендера, а следующая глава будет объединена в соответствии с ритмом этой главы.Интерпретация исходного кода Vue2 (семь)Раздел dom diff в Vue3 объясняет часть патча в Vue3 и медленно переваривает их все.