предисловие
Привет всем, я Делай. В прошлой статье мы говорили о первой части рендеринга. Если у вас есть какие-либо вопросы по предыдущей статье, оставьте сообщение. Давайте обсудим и узнаем вместе; в этой статье основное внимание будет уделено объяснению данных в Vue3 Что делать, когда происходит обновление.
Выпущен iPhone 12, и, посмотрев на цену, я думаю, что мой iPhone 7 plus можно использовать еще год.
Webpack5 был выпущен через два года, но выпуск новой версии обычно имеет некоторые ошибки.Когда все функции будут усовершенствованы, каждый может обновить и использовать его.
пример кода
В этой статье мы объясним dom diff, поэтому давайте объясним это на следующем примере Этот пример основан на предыдущей статье, где добавлено изменение данных, то есть значение списка изменилось. Кнопка изменения добавляется в html, и функция изменения вызывается нажатием кнопки изменения, чтобы изменить значение списка. Примеры есть в исходном коде/packages/vue/examples/classic/каталог, следующий код примера:
const app = Vue.createApp({
data() {
return {
list: ['a', 'b', 'c', 'd']
}
},
methods: {
change() {
this.list = ['a', 'd', 'e', 'b']
}
}
});
app.mount('#demo')
<!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>
<button @click="change">change</button>
</div>
<script src="./hello.js"></script>
</body>
</html>
Интерпретация исходного кода
Что касается изменения данных в Vue3, которое в конечном итоге влияет на процесс смены страницы, в этой статье объясняется только componentEffect и последующие коды.После изменения данных, как выполнить функцию componentEffect и почему выполняется componentEffect, в следующей статье будет объяснять.
componentEffect
Давайте посмотрим на код в части обновления componentEffect:
// @file packages/runtime-core/src/renderer.ts
function componentEffect() {
if (!instance.isMounted) {
// first render
} else {
let {next, bu, u, parent, vnode} = instance
let originNext = next
let vnodeHook: VNodeHook | null | undefined
if (next) {
updateComponentPreRender(instance, next, optimized)
} else {
next = vnode
}
next.el = vnode.el
// beforeUpdate hook
if (bu) {
invokeArrayFns(bu)
}
// onVnodeBeforeUpdate
if ((vnodeHook = next.props && next.props.onVnodeBeforeUpdate)) {
invokeVNodeHook(vnodeHook, parent, next, vnode)
}
const nextTree = renderComponentRoot(instance)
const prevTree = instance.subTree
instance.subTree = nextTree
if (instance.refs !== EMPTY_OBJ) {
instance.refs = {}
}
patch(
prevTree,
nextTree,
hostParentNode(prevTree.el!)!,
getNextHostNode(prevTree),
instance,
parentSuspense,
isSVG
)
next.el = nextTree.el
if (originNext === null) {
updateHOCHostEl(instance, nextTree.el)
}
// updated hook
if (u) {
queuePostRenderEffect(u, parentSuspense)
}
// onVnodeUpdated
if ((vnodeHook = next.props && next.props.onVnodeUpdated)) {
queuePostRenderEffect(() => {
invokeVNodeHook(vnodeHook!, parent, next!, vnode)
}, parentSuspense)
}
}
}
Когда данные изменяются, они в конечном итоге переходят к логической части else выше.
- По умолчанию next имеет значение null.Когда родительский компонент вызывает processComponent для запуска текущего вызова, это будет VNode.В настоящее время next имеет значение null;
- Вызвать функцию ловушки beforeUpdate текущего экземпляра, вызвать функцию ловушки beforeUpdate родительского компонента Vnode (следующего) для обновления;
- Получить vNode текущего экземпляра => prevTree, получить vNode для обновления => nextTree, затем вызвать patch;
Процесс вызова функции патча, то есть процесс взятия различных притоков в соответствии с типом VNode, нажмите кнопку изменения: значение n1:Значение n2:
По этому значению можно узнать, что оно попадет в функцию processFragment;
processFragment
передачаprocessFragment(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized)функция, значение параметра:
- В это время n1 и n2 такие, как показано выше;
- контейнер #demo;
- якорь нулевой;
- parentComponent — это экземпляр экземпляра;
- parentSuspense имеет значение null;
- isSVG ложно;
- оптимизировано ложно;
Взгляните на исходный код функции processFragment:
// @file packages/runtime-core/src/renderer.ts
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) {
// first render的逻辑
} else {
if (
patchFlag > 0 && patchFlag & PatchFlags.STABLE_FRAGMENT && dynamicChildren
) {
patchBlockChildren(
n1.dynamicChildren!,
dynamicChildren,
container,
parentComponent,
parentSuspense,
isSVG
)
if (__DEV__ && parentComponent && parentComponent.type.__hmrId) {
traverseStaticChildren(n1, n2)
} else if (
n2.key != null ||
(parentComponent && n2 === parentComponent.subTree)
) {
traverseStaticChildren(n1, n2, true /* shallow */)
}
} else {
patchChildren(
n1,
n2,
container,
fragmentEndAnchor,
parentComponent,
parentSuspense,
isSVG,
optimized
)
}
}
}
Удалив код первого рендера, вы можете увидеть, что следующий разделён на две ветки, по n1 и n2 мы возьмём ветвь if и выполним patchBlockChildren.
patchBlockChildren
передачаpatchBlockChildren(n1.dynamicChildren, n2.dynamicChildren, container, parentComponent, parentSuspense, isSVG)функции, параметры следующие:
- oldChildren: n1.dynamicChildren, представляющий собой массив из двух элементов: Symbol(Fragment) => ul и button;
- newChildren: n2.dynamicChildren, представляющий собой массив из двух элементов: Symbol(Fragment) => ul и button;
- fallbackContainer: контейнер #demo;
- parentComponent: экземпляр экземпляра;
- родительская приостановка: ноль;
- isSVG: ложь.
Взгляните на исходный код patchBlockChildren:
// @file packages/runtime-core/src/renderer.ts
const patchBlockChildren: PatchBlockChildrenFn = (
oldChildren,
newChildren,
fallbackContainer,
parentComponent,
parentSuspense,
isSVG
) => {
for (let i = 0; i < newChildren.length; i++) {
const oldVNode = oldChildren[i]
const newVNode = newChildren[i]
const container =
oldVNode.type === Fragment ||
!isSameVNodeType(oldVNode, newVNode) ||
oldVNode.shapeFlag & ShapeFlags.COMPONENT ||
oldVNode.shapeFlag & ShapeFlags.TELEPORT
? hostParentNode(oldVNode.el!)!
: fallbackContainer
patch(
oldVNode,
newVNode,
container,
null,
parentComponent,
parentSuspense,
isSVG,
true
)
}
}
Вы можете видеть, что patchBlockChildren — это цикл for, вызывающий функцию исправления, а newChildren — это массив длины 2. зацикливание вызововpatch(oldVNode, newVNode, container, null, parentComponent, parentSuspense, isSVG, true);
- Первый цикл:
-
- oldVNode: объект VNode, сгенерированный старым массивом ul;
-
- newVNode: объект VNode, сгенерированный новым массивом ul;
-
- контейнер: элемент ul;
-
- привязка: указанное выше передается нулевым значением;
-
- parentComponent: экземпляр экземпляра;
-
- parentSuspense: ноль;
-
- isSVG: ложь;
-
- оптимизировано: правда;
- Второй цикл:
-
- oldVNode: объект VNode, состоящий из старой кнопки изменения;
-
- newVNode: объект VNode, сформированный новой кнопкой изменения;
-
- контейнер: в настоящее время контейнер #demo;
-
- привязка: указанное выше передается нулевым значением;
-
- parentComponent: экземпляр экземпляра;
-
- parentSuspense: ноль;
-
- isSVG: ложь;
-
- оптимизировано: правда;
processElement
Давайте сначала поговорим о втором цикле, второй раз относительно прост; вышеупомянутый вызов функции патча, из вышеизложенного мы знаем, что тип второго цикла newVNode - это кнопка; он перейдет кprocessElement, все параметры передаются через:
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) {
// first render
} else {
patchElement(n1, n2, parentComponent, parentSuspense, isSVG, optimized)
}
}
Приведенный выше код будет напрямую вызывать patchElement, а параметры:
- n1: объект VNode, состоящий из старой кнопки изменения;
- n2: объект VNode, сформированный новой кнопкой изменения;
- parentComponent: экземпляр экземпляра;
- parentSuspense: ноль;
- isSVG: ложь;
- оптимизировано: правда;
patchChildren
Теперь поговорим о первом цикле, при выполнении патча тип newVNode — Symbol(Fragment) => ul, и он все равно пойдет наprocessFragmentфункция, но в это время dynamicChildren пуст, она будет продолжать работать до тех пор, покаpatchChildrenфункция.
patchChildren
В этот момент запустите функцию patchChildren, давайте посмотрим на запущенные до этого момента параметры:
- n1: объект VNode, сгенерированный старым массивом ul;
- n2: объект VNode, сгенерированный новым массивом ul;
- контейнер: элемент ul;
- привязка: объект, сгенерированный в конце ul;
- parentComponent: экземпляр экземпляра;
- parentSuspense: ноль
- isSVG: ложь;
- оптимизировано: правда;
Давайте посмотрим на исходный код patchChildren:
const patchChildren: PatchChildrenFn = (
n1,
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized = false
) => {
const c1 = n1 && n1.children
const prevShapeFlag = n1 ? n1.shapeFlag : 0
const c2 = n2.children
const {patchFlag, shapeFlag} = n2
if (patchFlag > 0) {
if (patchFlag & PatchFlags.KEYED_FRAGMENT) {
patchKeyedChildren(
c1 as VNode[],
c2 as VNodeArrayChildren,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
)
return
} else if (patchFlag & PatchFlags.UNKEYED_FRAGMENT) {
// patchUnkeyedChildren
return
}
}
// other ......
}
В настоящее время значение patchFlag равно 128, и у нашего рендеринга списка есть ключ, поэтому будет работатьpatchKeyedChildrenфункция, c1 — массив, составленный из четырех li (a, b, c, d); c2 — массив, составленный из новых li (a, d, e, b); остальные значения прозрачно передаются в patchKeyedChildren.
patchKeyedChildren
Параметры функции patchKeyedChildren были объяснены выше, здесь мы рассмотрим:
- c1: массив из четырех ли (a, b, c, d);
- c2: массив новых li (a, d, e, b);
- контейнер: элемент ul;
- parentAnchor: объект, сгенерированный в конце ul;
- parentComponent: экземпляр экземпляра;
- parentSuspense: ноль
- isSVG: ложь;
- оптимизировано: правда;
Далее смотрим исходный код функции patchKeyedChildren:
const patchKeyedChildren = (
c1: VNode[],
c2: VNodeArrayChildren,
container: RendererElement,
parentAnchor: RendererNode | null,
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
isSVG: boolean,
optimized: boolean
) => {
let i = 0
const l2 = c2.length
let e1 = c1.length - 1
let e2 = l2 - 1
while (i <= e1 && i <= e2) {
const n1 = c1[i]
const n2 = (c2[i] = optimized ? cloneIfMounted(c2[i] as VNode) : normalizeVNode(c2[i]))
if (isSameVNodeType(n1, n2)) {
patch(n1,n2,container,null,parentComponent,parentSuspense,isSVG,optimized)
} else {
break
}
i++
}
while (i <= e1 && i <= e2) {
const n1 = c1[e1]
const n2 = (c2[e2] = optimized ? cloneIfMounted(c2[e2] as VNode) : normalizeVNode(c2[e2]))
if (isSameVNodeType(n1, n2)) {
patch(n1,n2,container,null,parentComponent,parentSuspense,isSVG,optimized)
} else {
break
}
e1--
e2--
}
if (i > e1) {
if (i <= e2) {
const nextPos = e2 + 1
const anchor = nextPos < l2 ? (c2[nextPos] as VNode).el : parentAnchor
while (i <= e2) {
patch(
null,
(c2[i] = optimized
? cloneIfMounted(c2[i] as VNode)
: normalizeVNode(c2[i])),
container,
anchor,
parentComponent,
parentSuspense,
isSVG
)
i++
}
}
}
else if (i > e2) {
while (i <= e1) {
unmount(c1[i], parentComponent, parentSuspense, true)
i++
}
}
else {
const s1 = i
const s2 = i
for (i = s2; i <= e2; i++) {
const nextChild = (c2[i] = optimized ? cloneIfMounted(c2[i] as VNode) : normalizeVNode(c2[i]))
if (nextChild.key != null) {
keyToNewIndexMap.set(nextChild.key, i)
}
}
let j
let patched = 0
const toBePatched = e2 - s2 + 1
let moved = false
let maxNewIndexSoFar = 0
const newIndexToOldIndexMap = new Array(toBePatched)
for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0
for (i = s1; i <= e1; i++) {
const prevChild = c1[i]
if (patched >= toBePatched) {
unmount(prevChild, parentComponent, parentSuspense, true)
continue
}
let newIndex
if (prevChild.key != null) {
newIndex = keyToNewIndexMap.get(prevChild.key)
} else {
for (j = s2; j <= e2; j++) {
if (
newIndexToOldIndexMap[j - s2] === 0 &&
isSameVNodeType(prevChild, c2[j] as VNode)
) {
newIndex = j
break
}
}
}
if (newIndex === undefined) {
unmount(prevChild, parentComponent, parentSuspense, true)
} else {
newIndexToOldIndexMap[newIndex - s2] = i + 1
if (newIndex >= maxNewIndexSoFar) {
maxNewIndexSoFar = newIndex
} else {
moved = true
}
patch(
prevChild,
c2[newIndex] as VNode,
container,
null,
parentComponent,
parentSuspense,
isSVG,
optimized
)
patched++
}
}
const increasingNewIndexSequence = moved
? getSequence(newIndexToOldIndexMap)
: EMPTY_ARR
j = increasingNewIndexSequence.length - 1
for (i = toBePatched - 1; i >= 0; i--) {
const nextIndex = s2 + i
const nextChild = c2[nextIndex] as VNode
const anchor =
nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor
if (newIndexToOldIndexMap[i] === 0) {
patch(
null,
nextChild,
container,
anchor,
parentComponent,
parentSuspense,
isSVG
)
} else if (moved) {
if (j < 0 || i !== increasingNewIndexSequence[j]) {
move(nextChild, container, anchor, MoveType.REORDER)
} else {
j--
}
}
}
}
}
Приведенный выше код содержит два цикла while и две пары операторов if-else;
- i=0, цикл начинается с нижнего индекса, e1, e2 — длины c1 и c2, l2 — длина новых дочерних элементов;
- Первый цикл while перебирает список с самого начала:
-
- Когда тип узла тот же, вызовите patch;
-
- Когда nodeType отличается, выйдите из цикла;
- Второй цикл while, когда первый цикл while не прошел ни c1, ни c2, проходит с конца:
-
- Когда тип узла тот же, вызовите patch;
-
- Когда nodeType отличается, выйдите из цикла;
- Первый, если i>e1 доказывает, что был пройден c1, i
- Второй else-if, i>e2 доказывает, что был пройден c2, i
- Второй else: хотя бы один из c1 и c2 не был пройден, и логика достижения последнего else:
-
-
for (i = s2; i <= e2; i++)Цикл for проходит по оставшемуся c2, собирает ключ каждого элемента c2 и формирует map => keyToNewIndexMap;
-
-
-
for (i = 0; i < toBePatched; i++)Цикл for проходит по длине оставшейся части c2 для создания карты и присваивает ей значение 0;
-
-
-
for (i = s1; i <= e1; i++)Цикл for проходит по оставшемуся c1 и использует ключ, чтобы получить его напрямую (получается оставшийся c2 цикла for) newIndex,Здесь доказано, что надо привязывать ключ, очень важна уникальность;Значение newIndex указывает, что текущий старый элемент в c2 находится в c1, а старый preChild по-прежнему нужен в c2, затем вызовитеpatch; Если newIndex не определен, это означает, что старый preChild не нужен в c2, вызовите unmount, чтобы выгрузить текущий preChild;
-
-
- После прохождения оставшегося c1, пройдите оставшийся c2 в обратном направлении:
for (i = toBePatched - 1; i >= 0; i--);если(newIndexToOldIndexMap[i] === 0Это доказывает, что текущий nextChild является новым узлом, и вызывает patch, в противном случае оценивается, произошло ли перемещение до этого, и после логической оценки вызывается перемещение;
- После прохождения оставшегося c1, пройдите оставшийся c2 в обратном направлении:
пример patchKeyedChildren
Согласно нашему примеру выше, процесс перехода от старого: ['a', 'b', 'c', 'd'] к новому: ['a', 'd', 'e', 'b '] выглядит следующим образом:
- Сначала введите первый цикл while, в это время i равно 0, l2 равно 4, e1 равно 3 и e2 равно 3;
-
- Первый цикл, old-a и new-a одинаковы, патч вызова, без изменений;
-
- Второй цикл, old-b и new-b разные, перерыв;
-
- Выпрыгнуть из петли, петля с начала заканчивается;
- Войдите во второй цикл while, в этот раз i равно 1, l2 равно 4, e1 равно 3 и e2 равно 3;
-
- Первый цикл, old-d и new-b разные, break;
-
- Выпрыгнуть из петли, петля с хвоста заканчивается;
- Введите первый, если он признан ложным, введите второй, если он признан ложным, введите еще;
- Цикл for собирает ключ каждого элемента c2,keyToNewIndexMap = ['d' => 1, 'e' => 2, 'b' => 3];
- Создайте массив длины оставшейся длины c2newIndexToOldIndexMap = [0, 0 ,0];
- Введите в это время
for (i = s1; i <= e1; i++)цикл c1 for повторяет оставшуюся стадию, где i равно 1, 1 S1, 1 S2: -
- Первый цикл: пройденный элемент old-b, обнаруженный в new, а индекс в new, полученный через keyToNewIndexMap, равен 3, вызов patch;
-
- Второй цикл: пройденный элемент old-c, которого нет в new, вызов unmount для выгрузки текущего old-c, после измененияc1 это ['a', 'b', 'd']
-
- Третий цикл: пройденный элемент old-d, который существует в new, а индекс в new, полученный через keyToNewIndexMap, равен 1, вызов patch;
-
- Выпрыгните из цикла и пройдите оставшиеся этапы c1 до конца;
- Введите в это время
for (i = toBePatched - 1; i >= 0; i--)Пройдите оставшуюся стадию c2 в обратном направлении, в это время i равно 2, j равно 0, s1 равно 1, s2 равно 1, а newIndexToOldIndexMap равно [4, 0, 2]: -
- В первом цикле оцените, существует ли текущий nextChild (new-b), найдите, что nextChild существует через newIndexToOldIndexMap, а значение индекса в old равно 2, j--, j равно -1 в это время; i--, i равно 1;
-
- Во втором цикле оцените, существует ли текущий nextChild(new-e) или нет, и найдите, что значение индекса nextChild равно 0 через newIndexToOldIndexMap, указывая на то, что он не существует, затем вызовите patch; i--, i равно 0 ; после заменыc1 это ['а', 'е', 'б', 'г'];
-
- В третьем цикле оцените, существует ли текущий nextChild(new-d) или нет, и найдите, что значение индекса nextChild равно 4 через newIndexToOldIndexMap, что указывает на его существование, затем вызовите команду move;i--, i равно -1; после измененияc1 это ['a', 'd' 'e', 'b'];
-
- В этот момент i равно -1, выпрыгнуть из петли, петля заканчивается
- Обход завершен, а результат изменен на новый: ['a', 'd', 'e', 'b']
isSameVNodeType
Вы можете посмотреть на код isSameVNodeType ниже, Когда вы пишете код, чтобы улучшить производительность страницы и скорость dom diff, если нет элемента, который изменился, ключ должен остаться прежним, неv-for="(item, index) in list" :key="index"Это написано потому, что когда переместились только внутренние элементы массива, а элементы не изменились, меняется значение индекса, что вызовет недопонимание в программе во время dom diff.Уникальность ключа очень важна
export function isSameVNodeType(n1: VNode, n2: VNode): boolean {
return n1.type === n2.type && n1.key === n2.key
}
Эпилог
В этой главе в основном объясняется, что делает Vue3 во время операции обновления, уделяя особое внимание операции в dom diff, а также приводится пример для понимания всеми, вы можете сравнитьРазница Vue2С точки зрения Vue3 оптимизирован dom diff и имеет более высокую производительность, чем Vue2.
Конец этой главы также является общим прохождением основного процесса Vue 3. В последующих статьях будет объяснен ComponentAPI и реактивный.
Эпидемическая ситуация в Циндао на самом деле не серьезная.Это просто для того,чтобы дать всем душевное спокойствие и хорошую защиту.Что делать или что делать?Национальный тест на нуклеиновые кислоты вызвал у всех в Циндао панику,не бойтесь , верьте в Циндао, верьте в правительство, верьте в Китай.