首先接上文 vnode:juejin.im/post/5cf9e4…
之前我们先看一张图片,来了解一下diff到patch的流程图
diff做了什么
在数据变更后,生成新的dom树,新旧两份树进行对比
-
节点一一比对,判断如下内容
标签改变
属性变更
文本变更
节点删除
因兄弟节点删除造成的平移,或者与兄弟节点交换位置,那么也会触发上面的条件,这个显然不是想要的结果,为了判断是否是原有的节点变换了位置,所以加入了key,这也是为什么推荐填写唯一的key值,而很多人初学者直接使用了index,这样并没什么用处,只是消除了警告
这一块也比较复杂,所以不考虑,只做第一步的事情。主要以了解整体过程为主。
经过diff后我们要拿到的数据
type: ATTRS 属性变化, REPLACE: 标签变更,TEXT: 文本变更,REMOVE: 节点删除
现在开始写代码了
假设我们的数据变更了,那么就生成了一份新的virtualDom了
src/index.js
let virtualDom1 = createElement('ui', { class: 'li-group' }, [
createElement('li', { class: 'item' }, ['a']),
createElement('li', { class: 'item' }, ['b'])
])
let virtualDom2 = createElement('ui', { class: 'lis-group' }, [
createElement('div', { class: 'item' }, ['a']),
createElement('li', { class: 'item' }, ['2'])
])
复制代码
接下来对两份virtualDom进行diff
src/index.js
import diff from './diff'
let patches = diff(virtualDom1, virtualDom2)
复制代码
src/diff.js
const REMOVE = 'REMOVE'
const REPLACE = 'REPLACE'
const ATTRS = 'ATTRS'
const TEXT = 'TEXT'
function diff(oldThree, newThree) {
let patches = {}
let index = 0
// 递归比较
walk(oldThree, newThree, index, patches)
return patches;
}
function walk(oldNode, newNode, index, patches) {
if (!newNode) {
// 新节点不存在,既老节点被删除
patches[index] = { type: REMOVE }
} else if(isText(oldNode)&&isText(newNode)){
// 是文本的情况
if(oldNode !== newNode){
patches[index] = { type: TEXT, text: newNode }
}
} else if (oldNode.type !== newNode.type) {
// 标签变更
patches[index] = { type: REPLACE, newNode }
diffChildren(oldNode.children, newNode.children, patches)
} else if (oldNode.type === newNode.type) {
// diff属性
let attrs = diffAttr(oldNode.attrs, newNode.attrs)
if (Object.keys(attrs).length) {
patches[index] = { type: ATTRS, attrs }
}
// diff子节点
diffChildren(oldNode.children, newNode.children, patches)
}
}
// diff属性
function diffAttr(oldAttrs, newAttrs) {
let attrs = {}
for (let key in oldAttrs) {
if (oldAttrs[key] !== newAttrs[key]) {
attrs[key] = newAttrs[key]
}
}
for (let key in newAttrs) {
if (!oldAttrs.hasOwnProperty(key)) {
attrs[key] = newAttrs[key]
}
}
return attrs
}
// diff子节点
let INDEX = 0
function diffChildren(oldChildren, newChildren, patches) {
oldChildren.forEach((child, idx) => {
walk(child, newChildren[idx], ++INDEX, patches)
})
}
function isText(val){
return (typeof val === 'string' || typeof val === 'number')
}
export default diff
复制代码
拿到了patches我们就可以调用patch函数去修改dom了
src/index.js
import patch from './patch'
patch(el, patches)
复制代码
我们该写patch函数了
src/patch.js
import { render, Element, setAttr } from "./element";
let allPatches
let index = 0
function patch(node, patches) {
allPatches = patches
walk(node)
}
function walk(node) {
let currentPatch = allPatches[index++]
let childNodes = node.childNodes
if (childNodes) {
// 遍历所有节点
childNodes.forEach(child=>{
walk(child)
})
}
if (currentPatch) {
doPatch(node, currentPatch)
}
}
const REMOVE = 'REMOVE'
const REPLACE = 'REPLACE'
const ATTRS = 'ATTRS'
const TEXT = 'TEXT'
function doPatch(node, patches) {
switch (patches.type) {
case REMOVE:
node.remove()
break;
case REPLACE:
let newNode
if (patches.newNode instanceof Element) {
newNode = render(patches.newNode)
} else {
newNode = document.createTextNode(patches.newNode)
}
node.parentNode.replaceChild(newNode, node)
break;
case ATTRS:
// 挂载属性
for (let key in patches.attrs) {
let value = patches.attrs[key]
if (value) {
setAttr(node, key, value)
} else {
node.removeAttribute(key)
}
}
break;
case TEXT:
node.textContent = patches.text
break;
default:
break;
}
}
export default patch
复制代码
github: github.com/XueMary/my-…