一、感受 diff 算法
当父节点发生改变时,比如 ul 变为 ol ,里面的 li 不发生改变,diff 算法是会暴力删除的。
2. diff 算法处理新旧节点不是同一个节点时。
snabbdom 判断是否是相同的虚拟节点:
创建节点时,所有子节点需要递归创建的。
二、手写第一次上树时
1. 目录结构:
// index.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <button id='btn'>点我改变DOM</button> <div id="container"></div> <script src="/virtual/bundle.js"></script> </body> </html>
// index.js import h from './mySnabbdom/h' import patch from './mySnabbdom/patch' // var myVnode1 = h('div', {}, 'test') // var myVnode1 = h('div', {}, []) // var myVnode1 = h('div', {}, h()) // var myVnode1 = h('div', {}, [ // h('div', {}, '菠萝'), // h('div', {}, '香蕉'), // h('div', {}, [ // h('div', {}, '火龙果'), // h('div', {}, '牛油果'), // ]), // ]) const container = document.getElementById('container'); const btn = document.getElementById('btn'); var myVnode1 = h('ul', {}, [ h('li', {}, '火龙果'), h('li', {}, [ h('div', {}, [ h('ol', {}, [ h('li', {}, '哈哈哈'), h('li', {}, '嘿嘿嘿'), h('li', {}, '呵呵呵'), ]), ]), ]), ]); patch(container, myVnode1); var myVnode2 = h('div', {}, [ h('h1', {}, '你好'), h('h2', {}, '再见'), ]); btn.onclick = function() { patch(myVnode1, myVnode2); };
// patch.js import vnode from './vnode' import createElement from './createElement'; function isVnode(vnode) { return typeof(vnode.sel) !== 'undefined'; } function isSameNode(oldVnode, newVnode) { return oldVnode.key === newVnode.key && oldVnode.sel === newVnode.sel; } export default function(oldVnode, newVnode) { // 判断 oldVnode 是虚拟节点还是 DOM 节点 if (!isVnode(oldVnode)) { // oldVnode 是 container 节点,tagName 属性是大写 // 如果是 DOM 节点,就包装成虚拟节点 oldVnode = vnode(oldVnode.tagName.toLowerCase(), {}, [], undefined, oldVnode); // sel, data, children, text, elm } // 判断 oldVnode 和 newVnode 是不是同一个节点 (key和 选择器sel 相同) if (isSameNode(oldVnode, newVnode)) { console.log('是同一个节点'); } else { // 暴力插入新的,删除旧的 let newVnodeElm = createElement(newVnode, oldVnode.elm); // 插入到老节点之前 if (oldVnode.elm.parentNode && newVnodeElm) { oldVnode.elm.parentNode.insertBefore(newVnodeElm, oldVnode.elm); } // 删除老节点 oldVnode.elm.parentNode.removeChild(oldVnode.elm); } }
// createElement.js // 真正创建节点,将 vnode 创建为 DOM, 孤儿节点,不插入 export default function createElement(vnode) { // 标杆:pivot,在标杆前面插入新节点 vnode let domNode = document.createElement(vnode.sel); // 有子节点还是文本 if (vnode.text !== '' && (vnode.children == undefined || vnode.children.length == 0)) { // 内部是文字 domNode.innerText = vnode.text; } else if (Array.isArray(vnode.children) && vnode.children.length) { vnode.children.forEach(item => { let childVnodeElm = createElement(item); domNode.appendChild(childVnodeElm); }) } vnode.elm = domNode; // 补充 elm 属性 return vnode.elm; }
2. 界面效果: