Amybiubiu / Blog

6 stars 0 forks source link

virtual dom 和 key 值 #11

Open Amybiubiu opened 3 years ago

Amybiubiu commented 3 years ago

virtual dom 和 key 值

参考链接

virtual dom

尤大大的那篇回答讲的真好!不过我第三点倒是没怎么懂。以我看懂的大概来来说,用原生js写的话,各种 dom 操作会很复杂,参见网上的 demo。所以前端就要开发出一套框架来,可以更快捷编写前端页面。

至于框架的各种设计原则,我还不能理解,但是无论是何种框架,必然涉及到数据或者说UI更新的时候渲染问题。pure js 的数据和UI的更新思路是获取到目标节点,将新创建的节点 append 上去。如果框架使用最简单的更新方式可以是获取 body 元素,将所有新生成的节点作为 innerHTML,重新构建 dom 树,造成整个页面 rerender。

如果每次小的变动都重构 dom 树,比如一个 span 的文本发生变化,就把原先的 dom 树销毁,会对性能造成很大的影响。所以为了优化,react 引入了一层 virtual dom 的比较,获取到变动的节点,仅做必要的更新(具体过程暂时不懂,之后做补充),但 diff 算法是用 js 执行,所以有一句话是说“用 js 的计算换取 dom 树性能消耗”(可以参看浏览器的渲染过程思考这个问题)。总结一下,virtual dom 是为了相对 innnerHTML 的那类做法做的优化,并且不同的框架会有不同的优化方式,就是回答里的第三点。

key

首先 key 值需要是一个 stable 的值,如果理解这个 stable 呢?参考这两个 demo1demo2,前者用 index 做为 key 值,对于不同的 todo,key 值不是(仅)与其自身相关的,而是一个 index,是一个不稳定的 key 。每次的列表渲染前后,key 值列表都是一样的序列,对于 todo 中的 id, createdAt 值是来自参数,所以即使 key 值不变,在进一层比较后,会更新文本节点,而不会销毁重建,一定程度上实现了节点复用;但对于 input 来说,进一层比较后发现和之前一模一样,直接完整的节点复用了,造成了错误。而后者,key 是来自 todo 中的 id,每次的列表渲染 input 不会错误。并且如果不写 key 值,react 会默认使用 index 作为 key。

其次,key 值是起到了性能优化的作用,关于如何起作用的,待我看过 diff 过程再来补。具体的 diff 过程仍旧不明白,不过看官网那篇 reconciliation 文字,大概意思是说,在加了 key 值后,列表元素经过重排后,通过新旧比较后,key 相同的话,节点会通过移动复用,而不会销毁重建。

diff

中文的好像就只有这几篇相关的吧。blog1, blog2, blog3。看的也不是很懂,大概意思是说,react diff 只对树只进行同层级比较,不进行跨层级比较。同层级比较就是 n 个节点,比较 n 次,O(n) 和单颗树的遍历相同的复杂的。垮层级比较,一个双重循环 O(n^2),循环内节点编辑操作(创建、删除、移动)为 1 - n 步。所以复杂度为 O(n^3)。同层级比较的基于不同类型节点有不同树结构的假设,发现同一层级类型不同,销毁这颗子树即可。