前言:
读React 源码大概是最幸福的事情,因为在社区里有数之不尽的高手都会给出自己很独到的见解,即使有不懂的地方也会能找到前人努力挖掘的痕迹。我反问自己,然后即使在这样优越的环境下,还不去读源码,是不是就太懒了。 # 我会乱说? #
约定
这一篇都通过伪代码实现,保证首先从总体上走通流程,后面的篇章都是基于这样一个流程去实现。
开始之前
这里必须明确一个概念,所谓的 自定义标签
都是由很多原生标签诸如<div>
去实现的。
因此自定义标签
就可以想象成
1 2 3 |
<MyTag> <div class="MyTag"> <SomeTag></someTag> ===> <div class="someTag"></div> </MyTag> </div> |
流程
- 创建虚拟DOM
- 真实DOM 连接 虚拟DOM
- 视图更新
- 计算 [ 新虚拟DOM ] 和 [ 旧虚拟DOM ] 的差异
( diff )
- 根据计算的 差异, 更新真实DOM
( patch )
这里牵涉到两个词语 diff
和 patch
,稍后再解释,这里简单理解为 [计算差异]和[应用差异]。
伪代码实现
注:虽然这是这个系列的第一篇,但这已经是第二遍写了。原因是第一遍想完整模拟的时候发现,自己对算法的了解太粗浅,深搜和最短字符算法都不懂,最近和死月大大请教,所以这里偏向思路多一点。
1. 这里我们期望的真实DOM结构是这样的,下面我们一步步实现
1 2 3 |
<div id="wrap"> <span id="txt">i am some text</span> </div> |
2. 创建虚拟DOM
1 2 3 4 5 6 7 8 9 10 11 |
// 虚拟DOM的构造函数 function VirtualDOM(type,props,children){ this.type = type; this.props = props || {}; this.children = children || []; this.rootId = null; // 本节点id this.mountId = null; // 挂载节点id } var textDom = new VirtualDOM('span',{id:'txt'},'i am some text'); var wrapDom = new VirtualDOM('div',{id:'wrap'},[textDom]); |
3. 虚拟DOM不能够影响真实DOM,这里我们需要建立连接
最终目的得到这样的字符串,可以供真实DOM使用
1 |
"<div v-id="0"><span v-id="0.0">i am some text</span></div> |
简单实现 ( 这里需要记录一下每个DOM的id )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
var rootIdx = 0; // 标志每一个DOM的唯一id,用'.'区分层级 function mountElement(ele){ ele.rootId = rootIdx++; var tagOpen = '<' + ele.type + ' v-id="'+ ele.rootId +'">'; var tagClose = '</' + ele.type + '>'; var type; // 遍历拼凑出虚拟DOM的innerHTML ele.children.forEach(function(item,index){ item.rootId = ele.rootId + '.' + index; // 区分层级 item.moutId = ele.rootId; // 需要知道挂载在哪个对象上 if(Array.isArray(item.children)){ // 暂且用是否数组判断是否为文本dom tagOpen += mountElement(item); |