Last active
April 5, 2020 01:56
-
-
Save Arguseye/2cb001fb38e69de95582ee0e3216450e to your computer and use it in GitHub Desktop.
vitual dom 的demo
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>vitual dom</title> | |
| <script src="./build/compiled.js"></script> | |
| </head> | |
| <body> | |
| <div id="root"></div> | |
| <script> | |
| var root = document.getElementById('root'); | |
| render(root, view()); | |
| </script> | |
| </body> | |
| </html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // TODO: 这里的fatten有什么用? | |
| function flatten(arr) { | |
| return [].concat.apply([], arr); | |
| } | |
| function h(tag, props, ...children) { | |
| return { | |
| tag, | |
| props: props || {}, | |
| children: flatten(children) || [], | |
| }; | |
| } | |
| const nodePatchTypes = { | |
| CREATE: 'create node', | |
| REMOVE: 'remove node', | |
| REPLACE: 'replace node', | |
| UPDATE: 'update node' | |
| } | |
| const propPatchTypes = { | |
| REMOVE: 'remove prop', | |
| UPDATE: 'update prop' | |
| } | |
| let state = { num: 5 }; | |
| let timer; | |
| let preVDom; | |
| let dom; | |
| function render(parent) { | |
| // 初始化的VD | |
| const vdom = view(); | |
| preVDom = vdom; | |
| dom = createElement(vdom); | |
| parent.appendChild(dom); | |
| timer = setInterval(() => { | |
| state.num += 1; | |
| tick(parent); | |
| }, 500); | |
| } | |
| function tick(element) { | |
| if (state.num > 20) { | |
| clearTimeout(timer); | |
| return; | |
| } | |
| const newVDom = view(); | |
| // const newDom = createElement(newVDom); | |
| // element.replaceChild(newDom, dom); | |
| // dom = newDom; | |
| // 生成差异对象 | |
| const patchObj = diff(preVDom, newVDom); | |
| preVDom = newVDom; | |
| // 给 DOM 打个补丁 | |
| patch(element, patchObj); | |
| } | |
| function patch(parent, patchObj, index=0) { | |
| if (!patchObj) { | |
| return; | |
| } | |
| // 新建元素 | |
| if (patchObj.type === nodePatchTypes.CREATE) { | |
| return parent.appendChild(createElement(patchObj.vdom)); | |
| } | |
| // index 代表第几个child,如果不传时代表父元素 | |
| const element = parent.childNodes[index]; | |
| // 删除元素 | |
| if (patchObj.type === nodePatchTypes.REMOVE) { | |
| return parent.removeChild(element); | |
| } | |
| // 替换元素 | |
| if (patchObj.type === nodePatchTypes.REPLACE) { | |
| return parent.replaceChild(createElement(patchObj.vdom), element); | |
| } | |
| // 更新元素 | |
| if (patchObj.type === nodePatchTypes.UPDATE) { | |
| const {props, children} = patchObj; | |
| // 更新属性 | |
| patchProps(element, props); | |
| // 更新子元素 | |
| children.forEach( (patchObj, i) => { | |
| // 更新子元素时,需要将子元素的序号传入 | |
| patch(element, patchObj, i) | |
| }); | |
| } | |
| } | |
| // 更新属性 | |
| function patchProps(element, props) { | |
| if (!props) { | |
| return; | |
| } | |
| props.forEach( patchObj => { | |
| // 删除属性 | |
| if (patchObj.type === propPatchTypes.REMOVE) { | |
| element.removeAttribute(patchObj.key); | |
| } | |
| // 更新或新建属性 | |
| else if (patchObj.type === propPatchTypes.UPDATE) { | |
| element.setAttribute(patchObj.key, patchObj.value); | |
| } | |
| }) | |
| } | |
| function diff(oldVDom, newVDom) { | |
| // 新建 node | |
| if (oldVDom == undefined) { | |
| return { | |
| type: nodePatchTypes.CREATE, | |
| vdom: newVDom | |
| } | |
| } | |
| // 删除 node | |
| if (newVDom == undefined) { | |
| return { | |
| type: nodePatchTypes.REMOVE | |
| } | |
| } | |
| // 替换 node | |
| // babel 会将typeof重新写,https://stackoverflow.com/questions/57194480/how-to-prevent-babel-transform-operator-typeof-in-js | |
| // 判断oldVDom、newVDom不一样的情况。判断object、string、number | |
| // TODO: react里面究竟是怎么写的呢 | |
| if ( | |
| typeof oldVDom !== typeof newVDom || | |
| ((typeof oldVDom === 'string' || typeof oldVDom === 'number') && oldVDom !== newVDom) || | |
| oldVDom.tag !== newVDom.tag | |
| ) { | |
| return { | |
| type: nodePatchTypes.REPLACE, | |
| vdom: newVDom | |
| } | |
| } | |
| // 更新 node | |
| if (oldVDom.tag) { | |
| // 比较 props 的变化 | |
| const propsDiff = diffProps(oldVDom, newVDom); | |
| // 比较 children 的变化 | |
| const childrenDiff = diffChildren(oldVDom, newVDom); | |
| // 如果 props 或者 children 有变化,才需要更新 | |
| if (propsDiff.length > 0 || childrenDiff.some( patchObj => (patchObj !== undefined) )) { | |
| return { | |
| type: nodePatchTypes.UPDATE, | |
| props: propsDiff, | |
| children: childrenDiff | |
| } | |
| } | |
| } | |
| } | |
| // 比较 props 的变化 | |
| function diffProps(oldVDom, newVDom) { | |
| const patches = []; | |
| const allProps = {...oldVDom.props, ...newVDom.props}; | |
| // 获取新旧所有属性名后,再逐一判断新旧属性值 | |
| Object.keys(allProps).forEach((key) => { | |
| const oldValue = oldVDom.props[key]; | |
| const newValue = newVDom.props[key]; | |
| // 删除属性 | |
| if (newValue == undefined) { | |
| patches.push({ | |
| type: propPatchTypes.REMOVE, | |
| key | |
| }); | |
| } | |
| // 更新属性 | |
| else if (oldValue == undefined || oldValue !== newValue) { | |
| patches.push({ | |
| type: propPatchTypes.UPDATE, | |
| key, | |
| value: newValue | |
| }); | |
| } | |
| } | |
| ) | |
| return patches; | |
| } | |
| // 比较 children 的变化 | |
| function diffChildren(oldVDom, newVDom) { | |
| const patches = []; | |
| // 获取子元素最大长度 | |
| const childLength = Math.max(oldVDom.children.length, newVDom.children.length); | |
| // 遍历并diff子元素 | |
| for (let i = 0; i < childLength; i++) { | |
| patches.push(diff(oldVDom.children[i], newVDom.children[i])); | |
| } | |
| return patches; | |
| } | |
| function view() { | |
| return ( | |
| <div> | |
| Hello World {state.num} | |
| <ul> | |
| {[1, 2, 3].map((item, i) => ( | |
| <li id="1" class="li-1"> | |
| {i} | |
| </li> | |
| ))} | |
| <li>4</li> | |
| </ul> | |
| </div> | |
| ); | |
| } | |
| // 创建dom元素 | |
| function createElement(vdom) { | |
| const doc = document; | |
| // 如果vdom是字符串或者数字类型,则创建文本节点,比如“Hello World” | |
| if (typeof vdom === "string" || typeof vdom === "number") { | |
| return doc.createTextNode(vdom); | |
| } | |
| const { tag, props, children } = vdom; | |
| // 1. 创建元素 | |
| const element = doc.createElement(tag); | |
| // 2. 属性赋值 | |
| setProps(element, props); | |
| // 3. 创建子元素 | |
| // appendChild在执行的时候,会检查当前的this是不是dom对象,因此要bind一下 | |
| children.map(createElement).forEach(element.appendChild.bind(element)); | |
| return element; | |
| } | |
| // 属性赋值 | |
| function setProps(element, props) { | |
| for (let key in props) { | |
| element.setAttribute(key, props[key]); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment