Open vaakian opened 2 years ago
每个vdom元素都是一个Element,其中tag表示标签名,props表示dom属性,children表示元素内的孩子节点。
vdom
Element
tag
props
dom
children
function Element(tag, props, children = []) { this.tag = tag; this.props = props; this.children = children; if (props.key) { this.key = this.key; } let count = children.length; children.forEach((child, i) => { if (child instanceof Element) { // 是DOM节点,统计个数 count += child.count; } else { // 否则直接转换为字符串,当作渲染内容 children[i] = "" + child; } }); this.count = count; } // 创建一个vdom元素 let div = new Element('div', { class: 'container' }, children ['Hello, Vaakian!'])
通过new运算符比较繁杂,不太直观,所以再简写一个函数el
new
el
const el = function () { return new Element(...arguments); };
那么vdom就可以像这样创建:
const ul = el("ul", { class: "nav" }, [ el("li", { class: "nav-item" }, ["AAA"]), el("li", { class: "nav-item" }, ["BBB"]), el("li", { class: "nav-item" }, ["CCC"]) ]); const img = el("img", { src: "https://avatars.githubusercontent.com/u/30516060?v=4" }); const vdom = el("div", { class: "container" }, [ el("p", {}, ["Hello, World"]), ul, img ]);
以上通过自建vdom对象,描述出网页内dom的结构,那么下一步就是将vdom渲染成浏览器所能接受的真实dom元素。 主要是通过递归+前序遍历方式来创建,当然层序也能够完成,只是个人选择。
递归+前序遍历
function renderVDOM(vdom) { let dom = document.createElement(vdom.tag); // 设置属性 for (let [prop, value] of Object.entries(vdom.props)) { dom.setAttribute(prop, value); } // 渲染孩子节点 for (let child of vdom.children) { // 如果是Element,递归创建dom if (child instanceof Element) dom.append(renderVDOM(child)); // 否则是文本,直接插入 else dom.append(child); } return dom; }
测试结果
通过renderVDOM函数,可以将vdom渲染成真实可以挂载的dom元素对象,然后还需要提供一个类似react/vue的mount函数,也很简单:
renderVDOM
react/vue
mount
// 挂载 function mount(vdom, element) { element.append(renderVDOM(vdom)); }
那么创建、渲染、挂载完整的代码如下:
<body> <div id="app"></div> </body> <script> const ul = el("ul", { class: "nav" }, [ el("li", { class: "nav-item" }, ["AAA"]), el("li", { class: "nav-item" }, ["BBB"]), el("li", { class: "nav-item" }, ["CCC"]) ]); const img = el("img", { src: "https://avatars.githubusercontent.com/u/30516060?v=4" }); const vdom = el("div", { class: "container" }, [ el("p", {}, ["Hello, World"]), ul, img ]); mount(renderVDOM(vdom), document.getElementById('app')) </script>
在线预览: https://codesandbox.io/embed/vaakian-vdom-render-r7mgf?fontsize=14&hidenavigation=1&theme=dark
差异更新主要是指vdom元素变化,对应的真实dom也要变化。有两种解决方式,第一种就是最简单的把整颗vdom树全部重新渲染,替换原来的dom,这样显然是不可取的,有太多太多不必要的性能消耗。第二种就是差异更新,通过diff算法对比差异,然后对局部变更进行相应更新,使更新效率大大提升。
1. 创建
定义Element类
每个
vdom
元素都是一个Element
,其中tag
表示标签名,props
表示dom
属性,children
表示元素内的孩子节点。通过
new
运算符比较繁杂,不太直观,所以再简写一个函数el
创建vdom
那么vdom就可以像这样创建:
2. 渲染
以上通过自建
vdom
对象,描述出网页内dom的结构,那么下一步就是将vdom
渲染成浏览器所能接受的真实dom
元素。 主要是通过递归+前序遍历
方式来创建,当然层序也能够完成,只是个人选择。测试结果![image](https://user-images.githubusercontent.com/30516060/138474471-a3483549-c154-448f-aa38-7e1ca46ee2e7.png)
挂载
通过
renderVDOM
函数,可以将vdom渲染成真实可以挂载的dom元素对象,然后还需要提供一个类似react/vue
的mount
函数,也很简单:那么创建、渲染、挂载完整的代码如下:
在线预览: https://codesandbox.io/embed/vaakian-vdom-render-r7mgf?fontsize=14&hidenavigation=1&theme=dark