首先接上文 vnode:juejin.im/post/5cf9e4…node
以前咱們先看一張圖片,來了解一下diff到patch的流程圖git
在數據變動後,生成新的dom樹,新舊兩份樹進行對比github
節點一一比對,判斷以下內容bash
標籤改變dom
屬性變動函數
文本變動post
節點刪除ui
因兄弟節點刪除形成的平移,或者與兄弟節點交換位置,那麼也會觸發上面的條件,這個顯然不是想要的結果,爲了判斷是不是原有的節點變換了位置,因此加入了key,這也是爲何推薦填寫惟一的key值,而不少人初學者直接使用了index,這樣並沒什麼用處,只是消除了警告spa
這一塊也比較複雜,因此不考慮,只作第一步的事情。主要以瞭解總體過程爲主。code
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-…