前言
- 数据发生变化时 组件更新
- 新老虚拟节点 进行比对
- 最长递增子序列
- 主要通过
patch(老虚拟节点, 新虚拟节点, 父元素, 参照物)方法比对
- 有以下几种情况
- 类型不同直接替换
- 比对属性
- 老的是数组 新的是文本(孩子)
- 都是文本
- null 特殊情况 删除掉老的
- 老的是文本 现在是数组(孩子)
- 新的是数组 老的也是数组(孩子 重点讲diff算法)
- 新的是数组 老的也是数组有以下几种情况
- diff children 1 从头开始比 不同的就停止
- diff children 2 从尾开始比 不同的就停止
- diff children 3 老的少 新的多(有一方比对完成)
- diff children 4 老的多 新的少(有一方比对完成)
- diff children 5 乱序比较
示例
<script src="../node_modules/@vue/runtime-dom/dist/runtime-dom.global.js"></script>
<div id="app"></div>
<script>
const { createApp, h, reactive, ref } = VueRuntimeDOM
let App = {
setup(props, context) {
let flag = ref(true)
setTimeout(() => { flag.value = false }, 2000)
return () => {
return flag.value ?
h('div', { style: { color: '#000' } }, [
h('li', { key: 'a' }, 'a'),
h('li', { key: 'b' }, 'b'),
h('li', { key: 'c' }, 'c'),
h('li', { key: 'd', style: {color: 'green'}}, 'd'),
h('li', { key: 'e' }, 'e'),
h('li', { key: 'f' }, 'f'),
h('li', { key: 'g' }, 'g'),
]) :
h('div', { style: { color: 'blue' } }, [
h('li', { key: 'a' }, 'a'),
h('li', { key: 'b' }, 'b'),
h('li', { key: 'e' }, 'e'),
h('li', { key: 'c' }, 'c'),
h('li', { key: 'd', style: {color: 'red'}}, 'd'),
h('li', { key: 'h' }, 'h'),
h('li', { key: 'f' }, 'f'),
h('li', { key: 'g' }, 'g'),
])
}
},
}
let app = createApp(App, { name: 'zf', age: 12 })
app.mount('#app')
</script>
组件更新
const setupRenderEffect = (instance, container) => {
instance.update = effect(function componentEffect() {
if (!instance.isMounted) {
let proxyToUse = instance.proxy
let subTree = instance.subTree = instance.render.call(proxyToUse, proxyToUse)
patch(null, subTree, container)
instance.isMounted = true
} else {
const prevTree = instance.subTree
let proxyToUse = instance.proxy
const nextTree = instance.render.call(proxyToUse, proxyToUse)
instance.subTree = nextTree
patch(prevTree, nextTree, container)
}
}, {
scheduler: queueJob
})
}
新老元素不一样
const isSameVNodeType = (n1, n2) => {
return n1.type === n2.type && n1.key === n2.key
}
const unmount = (n1) => {
hostRemove(n1.el)
}
const patch = (n1, n2, container, anchor = null) => {
const { shapeFlag, type } = n2
if (n1 && !isSameVNodeType(n1, n2)) {
anchor = hostNextSibling(n1.el)
unmount(n1)
n1 = null
}
switch (type) {
case Text:
processText(n1, n2, container)
break;
default:
if (shapeFlag & ShapeFlags.ELEMENT) {
processElement(n1, n2, container, anchor)
} else if (shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
processComponent(n1, n2, container)
}
break;
}
}
元素一样
- 如果前后虚拟节点一样 将老的dom元素 复用
- 然后比对属性 和 孩子(子节点)
const patchElement = (n1, n2, container) => {
let el = (n2.el = n1.el)
const oldProps = n1.props || {}
const newProps = n2.props || {}
patchProps(oldProps, newProps, el)
patchChildren(n1, n2, el)
}
比对属性
const patchProps = (oldProps, newProps, el) => {
if (oldProps !== newProps) {
for (let key in newProps) {
const prev = oldProps[key]
const next = newProps[key]
if (prev !== next) {
hostPatchProp(el, key, prev, next)
}
}
for (const key in oldProps) {
if (!(key in newProps)) {
hostPatchProp(el, key, oldProps[key], null)
}
}
}
}
比对孩子(子节点)
const patchChildren = (n1, n2, el) => {
const c1 = n1.children
const c2 = n2.children
const prevShapeFlag = n1.shapeFlag
const shapeFlag = n2.shapeFlag
if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {
unmountChildren(c1)
}
if (c1 !== c2) {
hostSetElementText(el, c2)
}
} else {
if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {
if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
patchKeyedChildren(c1, c2, el)
} else {
unmountChildren(c1)
}
} else {
if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {
hostSetElementText(el, '');
}
if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
mountChildren(c2, el);
}
}
}
}
比对孩子(子节点) 核心diff算法
- 孩子 都是数组 进行比对
- vue3采用从头开始比较,两个尾指针
- vue2采用的是双指针算法
从头开始(sync form start)
const patchKeyedChildren = (c1, c2, el) => {
let i = 0
let e1 = c1.length - 1
let e2 = c2.length - 1
while (i <= e1 && i <= e2) {
const n1 = c1[i]
const n2 = c2[i]
if (isSameVNodeType(n1, n2)) {
patch(n1, n2, el)
} else {
break;
}
i++;
}
}
从尾开始(sync from end)
const patchKeyedChildren = (c1, c2, el) => {
while (i <= e1 && i <= e2) {
const n1 = c1[e1]
const n2 = c2[e2]
if (isSameVNodeType(n1, n2)) {
patch(n1, n2, el)
} else {
break;
}
e1--;
e2--;
}
}
老的少 新的多(common sequence + mount)
- 前提是有一方 已经结束
- 可能是在后面添加 也可能是在前面添加
- 判断有没有下一个节点 加在下一个节点前面
- 没有就是
appendchild往后添加
const patchKeyedChildren = (c1, c2, el) => {
if (i > e1) {
if (i <= e2) {
const nextPos = e2 + 1
const anchor = nextPos < c2.length ? c2[nextPos].el : null
while (i <= e2) {
patch(null, c2[i], el, anchor)
i++;
}
}
}
}
老的多 新的少(common sequence + unmount)
const patchKeyedChildren = (c1, c2, el) => {
if (i > e1) {
} else if (i > e2) {
while (i <= e1) {
unmount(c1[i])
i++;
}
} else {}
}
乱序比较(unknown sequence)
步骤一
- 创建 新的虚拟dom
{key => index} 映射表
- 在新的里面去寻找老的
key
- 没有直接删除
- 如果在新的里面找到 进行比对
- 问题:
- 位置没有移动, 只是把当前已有的进行了比对
const patchKeyedChildren = (c1, c2, el) => {
if (i > e1) {
} else if (i > e2) {
} else {
let s1 = i
let s2 = i
const keyToNewIndexMap = new Map()
for (let i = s2; i <= e2; i++) {
const childVNode = c2[i]
keyToNewIndexMap.set(childVNode.key, i)
}
for (let i = s1; i<=e1 ; i++) {
const oldVnode = c1[i]
let newIndex = keyToNewIndexMap.get(oldVnode.key)
if(newIndex === undefined) {
unmount(oldVnode)
} else {
patch(oldVnode, c2[newIndex], el)
}
}
}
}
步骤二
- 插入老的不存在的节点
- 首先创建一个新的索引对应老的位置的索引数组
- 从后往前插入方法 将节点一一插入
- 问题:
- 有些节点的位置是没必要移动的 如图 c,d
- 我们只要移动e就好(最长递增长子序列)
const patchKeyedChildren = (c1, c2, el) => {
if (i > e1) {
} else if (i > e2) {
} else {
let s1 = i
let s2 = i
const keyToNewIndexMap = new Map()
for (let i = s2; i <= e2; i++) {
const childVNode = c2[i]
keyToNewIndexMap.set(childVNode.key, i)
}
const toBePatched = e2 - s2 + 1
const newIndexToOldIndexMap = new Array(toBePatched).fill(0)
for (let i = s1; i<=e1 ; i++) {
const oldVnode = c1[i]
let newIndex = keyToNewIndexMap.get(oldVnode.key)
if(newIndex === undefined) {
unmount(oldVnode)
} else {
newIndexToOldIndexMap[newIndex - s2] = i + 1
patch(oldVnode, c2[newIndex], el)
}
}
for (let i = toBePatched - 1; i >= 0; i--) {
let currentIndex = i + s2
let child = c2[currentIndex]
let anchor = currentIndex + 1 < c2.length ? c2[currentIndex+1].el : null
if(newIndexToOldIndexMap[i] == 0) {
patch(null, child, el, anchor)
} else {
hostInsert(child.el, el, anchor)
}
}
}
}
步骤三
const patchKeyedChildren = (c1, c2, el) => {
if (i > e1) {
} else if (i > e2) {
} else {
let s1 = i
let s2 = i
const keyToNewIndexMap = new Map()
for (let i = s2; i <= e2; i++) {
const childVNode = c2[i]
keyToNewIndexMap.set(childVNode.key, i)
}
const toBePatched = e2 - s2 + 1
const newIndexToOldIndexMap = new Array(toBePatched).fill(0)
for (let i = s1; i<=e1 ; i++) {
const oldVnode = c1[i]
let newIndex = keyToNewIndexMap.get(oldVnode.key)
if(newIndex === undefined) {
unmount(oldVnode)
} else {
newIndexToOldIndexMap[newIndex - s2] = i + 1
patch(oldVnode, c2[newIndex], el)
}
}
let increasingNewIndexSequence = getSequence(newIndexToOldIndexMap)
let j = increasingNewIndexSequence.length - 1
for (let i = toBePatched - 1; i >= 0; i--) {
let currentIndex = i + s2
let child = c2[currentIndex]
let anchor = currentIndex + 1 < c2.length ? c2[currentIndex+1].el : null
if(newIndexToOldIndexMap[i] == 0) {
patch(null, child, el, anchor)
} else {
if(i != increasingNewIndexSequence[j]){
hostInsert(child.el, el, anchor)
} else {
j--;
}
}
}
}
}
function getSequence(arr) {
const len = arr.length
const result = [0]
const p = arr.slice(0)
let start;
let end;
let middle;
for (let i = 0; i < len; i++) {
const arrI = arr[i]
if (arrI !== 0) {
let resultLastIndex = result[result.length - 1]
if (arr[resultLastIndex] < arrI) {
p[i] = resultLastIndex
result.push(i)
continue
}
start = 0;
end = result.length - 1;
while (start < end) {
middle = ((start + end) / 2) | 0
if (arr[result[middle]] < arrI) {
start = middle + 1
} else {
end = middle
}
}
if (arrI < arr[result[start]]) {
if (start > 0) {
p[i] = result[start - 1]
}
result[start] = i
}
}
}
let len1 = result.length
let last = result[len1 - 1]
while (len1-- > 0) {
result[len1] = last
last = p[last]
}
return result
}
最长递增子序列
- [2, 3, 1, 5, 6, 8, 7, 9, 4] => [0, 1, 3, 4, 6, 7]
vue3源码最长递增子序列地址
* 贪心 + 二分算法如下
* 2
* 2 3
* 1 3
* 1 3 5
* 1 3 5 6
* 1 3 5 6 8
* 1 3 5 6 7
* 1 3 5 6 7 9
* 1 3 4 6 7 9 值
* | | | | | |
* 2 1 8 4 6 7 索引(第一步的result)
* 2 3 1 5 6 8 7 9 4 原数组值
* | | | | | | | | |
* X 0 X 1 3 4 4 6 1 push和二分查找的时候对应的上一个索引(p)
* 2 3 5 6 7 9 希望结果值
* | | | | | |
* 0 1 3 4 6 7 希望结果索引(result最终返回结果)
第一步
function getSequence(arr) {
const len = arr.length
const result = [0]
let start;
let end;
let middle;
for (let i = 0; i < len; i++) {
const arrI = arr[i]
if (arrI !== 0) {
let resultLastIndex = result[result.length - 1]
if (arr[resultLastIndex] < arrI) {
result.push(i)
continue
}
start = 0
end = result.length - 1
while (start < end) {
middle = ((start + end) / 2) | 0
if (arr[result[middle]] < arrI) {
start = middle + 1
} else {
end = middle
}
}
result[start] = i
}
}
return result
}
第二步
function getSequence(arr) {
const len = arr.length
const result = [0]
const p = arr.slice(0)
let start;
let end;
let middle;
for (let i = 0; i < len; i++) {
const arrI = arr[i]
if (arrI !== 0) {
let resultLastIndex = result[result.length - 1]
if (arr[resultLastIndex] < arrI) {
p[i] = resultLastIndex
result.push(i)
continue
}
start = 0;
end = result.length - 1;
while (start < end) {
middle = ((start + end) / 2) | 0
if (arr[result[middle]] < arrI) {
start = middle + 1
} else {
end = middle
}
}
if (arrI < arr[result[start]]) {
if (start > 0) {
p[i] = result[start - 1]
}
result[start] = i
}
}
}
console.log('求p', p)
return result
}
第三步
function getSequence(arr) {
const len = arr.length
const result = [0]
const p = arr.slice(0)
let start;
let end;
let middle;
for (let i = 0; i < len; i++) {
const arrI = arr[i]
if (arrI !== 0) {
let resultLastIndex = result[result.length - 1]
if (arr[resultLastIndex] < arrI) {
p[i] = resultLastIndex
result.push(i)
continue
}
start = 0;
end = result.length - 1;
while (start < end) {
middle = ((start + end) / 2) | 0
if (arr[result[middle]] < arrI) {
start = middle + 1
} else {
end = middle
}
}
if (arrI < arr[result[start]]) {
if (start > 0) {
p[i] = result[start - 1]
}
result[start] = i
}
}
}
let len1 = result.length
let last = result[len1 - 1]
while (len1-- > 0) {
result[len1] = last
last = p[last]
}
return result
}
组件渲染流程图
shard runtime-dom runtime-core
renderer.ts
import { effect } from "@vue/reactivity/src"
import { ShapeFlags } from "@vue/shared/src"
import { createAppAPI } from "./apiCreateApp"
import { createComponentInstance, setupComponent } from "./component"
import { queueJob } from "./scheduler"
import { normalizeVNode ,Text} from "./vnode"
export function createRenderer(rendererOptions) {
const {
insert: hostInsert,
remove: hostRemove,
patchProp: hostPatchProp,
createElement: hostCreateElement,
createText: hostCreateText,
createComment: hostCreateComment,
setText: hostSetText,
setElementText: hostSetElementText,
nextSibling: hostNextSibling,
} = rendererOptions
const setupRenderEffect = (instance, container) => {
instance.update = effect(function componentEffect() {
if (!instance.isMounted) {
let proxyToUse = instance.proxy
let subTree = instance.subTree = instance.render.call(proxyToUse, proxyToUse)
patch(null, subTree, container)
instance.isMounted = true
} else {
const prevTree = instance.subTree
let proxyToUse = instance.proxy
const nextTree = instance.render.call(proxyToUse, proxyToUse)
patch(prevTree, nextTree, container)
}
}, {
scheduler: queueJob
})
}
const mountComponent = (initialVNode, container) => {
const instance = (initialVNode.component = createComponentInstance(initialVNode))
setupComponent(instance)
setupRenderEffect(instance, container)
}
const processComponent = (n1, n2, container) => {
if (n1 == null) {
mountComponent(n2, container)
} else {
}
}
const mountChildren = (children, container) => {
for (let i = 0; i < children.length; i++) {
let child = normalizeVNode(children[i])
patch(null, child, container)
}
}
const mountElement = (vnode, container, anchor = null) => {
const { props, shapeFlag, type, children } = vnode
let el = (vnode.el = hostCreateElement(type))
if (props) {
for (const key in props) {
hostPatchProp(el, key, null, props[key]);
}
}
if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
hostSetElementText(el, children)
} else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
mountChildren(children, el)
}
hostInsert(el, container, anchor)
}
const processElement = (n1, n2, container, anchor) => {
if (n1 == null) {
mountElement(n2, container, anchor)
} else {
patchElement(n1, n2, container)
}
}
const processText = (n1, n2, container) => {
if (n1 == null) {
hostInsert((n2.el = hostCreateText(n2.children)), container)
}
}
const isSameVNodeType = (n1, n2) => {
return n1.type === n2.type && n1.key === n2.key
}
const unmount = (n1) => {
hostRemove(n1.el)
}
const unmountChildren = (children) => {
for (let i = 0; i < children.length; i++) {
unmount(children[i])
}
}
const patchProps = (oldProps, newProps, el) => {
if (oldProps !== newProps) {
for (let key in newProps) {
const prev = oldProps[key]
const next = newProps[key]
if (prev !== next) {
hostPatchProp(el, key, prev, next)
}
}
for (const key in oldProps) {
if (!(key in newProps)) {
hostPatchProp(el, key, oldProps[key], null)
}
}
}
}
const patchKeyedChildren = (c1, c2, el) => {
let i = 0
let e1 = c1.length - 1
let e2 = c2.length - 1
while (i <= e1 && i <= e2) {
const n1 = c1[i]
const n2 = c2[i]
if (isSameVNodeType(n1, n2)) {
patch(n1, n2, el)
} else {
break;
}
i++;
}
while (i <= e1 && i <= e2) {
const n1 = c1[e1]
const n2 = c2[e2]
if (isSameVNodeType(n1, n2)) {
patch(n1, n2, el)
} else {
break;
}
e1--;
e2--;
}
if (i > e1) {
if (i <= e2) {
const nextPos = e2 + 1
const anchor = nextPos < c2.length ? c2[nextPos].el : null
while (i <= e2) {
patch(null, c2[i], el, anchor)
i++;
}
}
} else if (i > e2) {
while (i <= e1) {
unmount(c1[i])
i++;
}
} else {
let s1 = i
let s2 = i
const keyToNewIndexMap = new Map()
for (let i = s2; i <= e2; i++) {
const childVNode = c2[i]
keyToNewIndexMap.set(childVNode.key, i)
}
const toBePatched = e2 - s2 + 1
const newIndexToOldIndexMap = new Array(toBePatched).fill(0)
for (let i = s1; i<=e1 ; i++) {
const oldVnode = c1[i]
let newIndex = keyToNewIndexMap.get(oldVnode.key)
if(newIndex === undefined) {
unmount(oldVnode)
} else {
newIndexToOldIndexMap[newIndex - s2] = i + 1
patch(oldVnode, c2[newIndex], el)
}
}
let increasingNewIndexSequence = getSequence(newIndexToOldIndexMap)
let j = increasingNewIndexSequence.length - 1
for (let i = toBePatched - 1; i >= 0; i--) {
let currentIndex = i + s2
let child = c2[currentIndex]
let anchor = currentIndex + 1 < c2.length ? c2[currentIndex+1].el : null
if(newIndexToOldIndexMap[i] == 0) {
patch(null, child, el, anchor)
} else {
if(i != increasingNewIndexSequence[j]){
hostInsert(child.el, el, anchor)
} else {
j--;
}
}
}
}
}
const patchChildren = (n1, n2, el) => {
const c1 = n1.children
const c2 = n2.children
const prevShapeFlag = n1.shapeFlag
const shapeFlag = n2.shapeFlag
if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {
unmountChildren(c1)
}
if (c1 !== c2) {
hostSetElementText(el, c2)
}
} else {
if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {
if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
patchKeyedChildren(c1, c2, el)
} else {
unmountChildren(c1)
}
} else {
if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {
hostSetElementText(el, '');
}
if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
mountChildren(c2, el);
}
}
}
}
const patchElement = (n1, n2, container) => {
let el = (n2.el = n1.el)
const oldProps = n1.props || {}
const newProps = n2.props || {}
patchProps(oldProps, newProps, el)
patchChildren(n1, n2, el)
}
const patch = (n1, n2, container, anchor = null) => {
const { shapeFlag, type } = n2
if (n1 && !isSameVNodeType(n1, n2)) {
anchor = hostNextSibling(n1.el)
unmount(n1)
n1 = null
}
switch (type) {
case Text:
processText(n1, n2, container)
break;
default:
if (shapeFlag & ShapeFlags.ELEMENT) {
processElement(n1, n2, container, anchor)
} else if (shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
processComponent(n1, n2, container)
}
break;
}
}
const render = (vnode, container) => {
patch(null, vnode, container)
}
function getSequence(arr) {
const len = arr.length
const result = [0]
const p = arr.slice(0)
let start;
let end;
let middle;
for (let i = 0; i < len; i++) {
const arrI = arr[i]
if (arrI !== 0) {
let resultLastIndex = result[result.length - 1]
if (arr[resultLastIndex] < arrI) {
p[i] = resultLastIndex
result.push(i)
continue
}
start = 0;
end = result.length - 1;
while (start < end) {
middle = ((start + end) / 2) | 0
if (arr[result[middle]] < arrI) {
start = middle + 1
} else {
end = middle
}
}
if (arrI < arr[result[start]]) {
if (start > 0) {
p[i] = result[start - 1]
}
result[start] = i
}
}
}
let len1 = result.length
let last = result[len1 - 1]
while (len1-- > 0) {
result[len1] = last
last = p[last]
}
return result
}
return {
createApp: createAppAPI(render)
}
}
完