dom-diff学习
dom-diff学习
介绍
本次介绍的是dom-diff
的逻辑,他对于我们在使用相关框架时,能更好的书写逻辑,避免出现一些问题的出现。
开始
snabbdom
snabbdom是一个精简化、模块化、功能强大、性能卓越的虚拟 DOM 库。
几乎包含了虚拟 Dom 相关模块的所有功能,并且也提供了跨端的支持。
通过查看他的源码可以更好的理解diff
原理。
主要特点
- 200 行 - 你可以通过简单地阅读所有核心代码来充分理解其工作原理
- 通过模块化实现可拓展
- 对于 vnode 和全局模块都提供了 hook,你可以在 patch 过程或者其他地方调用 hook
- 性能卓越:Snabbdom 是目前最高效的虚拟 DOM 库之一
- Patch 函数有一个相当于 reduce/scan 函数的函数声明,这将更容易集成其他函数式库
使用
1 |
|
虚拟 Dom
虚拟 Dom 是一个 javascript 对象结构。
大致为以下形式。
1 |
|
作用
- 模板引擎可以简化视图操作,没办法跟踪状态,虚拟 Dom 可以
- 通过比较前后两次状态差异更新真实 DOM,减少 Dom 操作
- 可以跨平台使用
- 创建内存开销小,性能更好(复杂视图情况)
Diff
简单描述
init
初始化,返回patch
函数。h
函数创建虚拟 Dom 对象。patch
比较新旧虚拟 Dom。- 将发生变化的 Dom 更新反映到真实 Dom 树当中。
细节
- init
采用闭包的形式,内部创建变量
1 |
|
- h
1 |
|
- vnode
1 |
|
patch
1 |
|
patchVnode
1 |
|
updateChildren
-
五种情况的处理
- 新开始节点 vs 旧开始节点(new_start_index++, old_start_index++)
- 新结束节点 vs 旧结束节点(new_end_index–, old_end_index–)
- 新结束节点 vs 旧开始节点(new_end_index–, old_start_index++)
- 新开始节点 vs 旧结束节点(new_start_index++, old_end_index–)
- 在 旧节点 中寻找 新开始节点,找到则位移到 旧开始节点 的位置,否则创建到 旧开始节点 的位置。
每次循环只需执行通过上述一个条件即可。
当新或者旧节点遍历完成则退出循环。
1 |
|
- 下面是一个包含前四种情况的一个例子
step | new_start | new_end | old_start | old_end |
---|---|---|---|---|
1 | 0 | 4 | 0 | 3 |
step | new_start | new_end | old_start | old_end |
---|---|---|---|---|
1 | 0 | 4 | 0 | 3 |
2 | 1 | 4 | 1 | 3 |
step | new_start | new_end | old_start | old_end |
---|---|---|---|---|
1 | 0 | 4 | 0 | 3 |
2 | 1 | 4 | 1 | 3 |
3 | 1 | 3 | 1 | 2 |
step | new_start | new_end | old_start | old_end |
---|---|---|---|---|
1 | 0 | 4 | 0 | 3 |
2 | 1 | 4 | 1 | 3 |
3 | 1 | 3 | 1 | 2 |
4 | 2 | 3 | 1 | 1 |
step | new_start | new_end | old_start | old_end |
---|---|---|---|---|
1 | 0 | 4 | 0 | 3 |
2 | 1 | 4 | 1 | 3 |
3 | 1 | 3 | 1 | 2 |
4 | 2 | 3 | 1 | 1 |
5 | 3 | 3 | 2 | 1 |
while
循环至此全部结束。
接着就是下面的收尾工作。
将剩下的节点插入到newEndIndex
节点前。
- 接着是一个第五种情况的例子
上面的例子,只说新节点的第一个子节点。
前面四种情况都无法满足。
1 |
|
因为是第一次进入,所以oldKeyIndex
为undefined
。
创建旧节点的map
,结构如下:
1 |
|
newStartVnode
当前为新节点下的第一个子节点,key
为a
。
所以idxInOld
不为undefined
。
接着拿到相关旧节点elmToMove
,且sel
相同。
最后比较两个节点的子节点。
将元素插入到旧开始节点的前面。
进入下一个循环。
React
以下为个人理解,如有问题请指出。
react的diff
和上面的库的逻辑基本大同小异,但是在16以后,就发生了一点变化。
Fiber
上面的diff
存在一个问题,就是在递归比较vnode的同时,也进行了dom操作,当产生大量的更新时,页面还是会出现卡顿的情况。
在16以后,引入了fiber
的概念,其数据结构其实就是一个链表。
fiber
中包含了很多相关信息,包括sibling
、child
、return
这些层级关系,以及effectTag
、nextEffect
、lastEffect
等等。
将整个更新过程分成了几个阶段:
- render
将vnode
转换为fiber
结构。
将需要更新节点打上effectTag
标记。
此过程可以被打断(存在一个scheduler进行调度),并且不同的更新时存在优先级的。 - commit
真正的更新过程。
无法被中断。
关于Key
通过上面的介绍,可以看出,key
属性在diff
过程中非常重要。
- 他可以使过程变得更快
- 也可以避免出错
- 并且使用索引作为
key
也会发生未知的错误
参考深入理解 React Diff 算法中的例子。
不设置key的情况
假设没有设置key
,在第三次循环当中,因为被判断成了是相同节点,多了一次patch
的过程。
当设置了key
的情况,在第三次循环时,会走上面的情况三(new_end vs old_start)。
设置索引为key的情况
根据上图显示,因为使用了索引作为key
,在新增了一个节点d到开始位置时。
diff
判断为情况一,所以节点a的颜色属性被赋值到了节点d上,引发了问题。
结束
参考
深入理解 React Diff 算法
DIff 算法看不懂就一起来砍我(带图)
图解 React 的 diff 算法:核心就两个字 —— 复用
react-source-code-debug
React Fiber 源码解析
snabbdom
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!