You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
var ReactElement = function(type, key, ref, self, source, owner, props) {
var element = {
// This tag allow us to uniquely identify this as a React Element
$$typeof: REACT_ELEMENT_TYPE,//重点在这里
// Built-in properties that belong on the element
type: type,
key: key,
ref: ref,
props: props,
// Record the component responsible for creating this element.
_owner: owner,
};
return element;
};
...
'use strict';
// The Symbol used to tag the ReactElement type. If there is no native Symbol
// nor polyfill, then a plain number is used for performance.
var REACT_ELEMENT_TYPE =
(typeof Symbol === 'function' && Symbol.for && Symbol.for('react.element')) ||
0xeac7;
module.exports = REACT_ELEMENT_TYPE;
/**
* Diff two list in O(N).
* @param {Array} oldList - Original List
* @param {Array} newList - List After certain insertions, removes, or moves
* @return {Object} - {moves: <Array>}
* - moves is a list of actions that telling how to remove and insert
*/
function diff (oldList, newList, key) {
var oldMap = makeKeyIndexAndFree(oldList, key)
var newMap = makeKeyIndexAndFree(newList, key)
var newFree = newMap.free
var oldKeyIndex = oldMap.keyIndex
var newKeyIndex = newMap.keyIndex
var moves = []
// a simulate list to manipulate
var children = []
var i = 0
var item
var itemKey
var freeIndex = 0
// first pass to check item in old list: if it's removed or not
// 遍历旧的集合
while (i < oldList.length) {
item = oldList[i]
itemKey = getItemKey(item, key)//itemKey a
// 是否可以取到
if (itemKey) {
// 判断新集合中是否有这个属性,如果没有则push null
if (!newKeyIndex.hasOwnProperty(itemKey)) {
children.push(null)
} else {
// 如果有 去除在新列表中的位置
var newItemIndex = newKeyIndex[itemKey]
children.push(newList[newItemIndex])
}
} else {
var freeItem = newFree[freeIndex++]
children.push(freeItem || null)
}
i++
}
// children [{id:"a"},{id:"b"},{id:"c"},null,{id:"e"}]
var simulateList = children.slice(0)//[{id:"a"},{id:"b"},{id:"c"},null,{id:"e"}]
// remove items no longer exist
i = 0
while (i < simulateList.length) {
if (simulateList[i] === null) {
remove(i)
removeSimulate(i)
} else {
i++
}
}
// i is cursor pointing to a item in new list
// j is cursor pointing to a item in simulateList
var j = i = 0
while (i < newList.length) {
item = newList[i]
itemKey = getItemKey(item, key)//c
var simulateItem = simulateList[j] //{id:"a"}
var simulateItemKey = getItemKey(simulateItem, key)//a
if (simulateItem) {
if (itemKey === simulateItemKey) {
j++
} else {
// 新增项,直接插入
if (!oldKeyIndex.hasOwnProperty(itemKey)) {
insert(i, item)
} else {
// if remove current simulateItem make item in right place
// then just remove it
var nextItemKey = getItemKey(simulateList[j + 1], key)
if (nextItemKey === itemKey) {
remove(i)
removeSimulate(j)
j++ // after removing, current j is right, just jump to next one
} else {
// else insert item
insert(i, item)
}
}
}
} else {
insert(i, item)
}
i++
}
//if j is not remove to the end, remove all the rest item
var k = simulateList.length - j
while (j++ < simulateList.length) {
k--
remove(k + i)
}
// 记录旧的列表中移除项 {index:3,type:0}
function remove (index) {
var move = {index: index, type: 0}
moves.push(move)
}
function insert (index, item) {
var move = {index: index, item: item, type: 1}
moves.push(move)
}
// 删除simulateList中null
function removeSimulate (index) {
simulateList.splice(index, 1)
}
return {
moves: moves,
children: children
}
}
/**
* Convert list to key-item keyIndex object.
* 将列表转换为 key-item 的键值对象
* [{id: "a"}, {id: "b"}, {id: "c"}, {id: "d"}, {id: "e"}] -> [a:0,b:1,c:2...]
* @param {Array} list
* @param {String|Function} key
*/
function makeKeyIndexAndFree (list, key) {
var keyIndex = {}
var free = []
for (var i = 0, len = list.length; i < len; i++) {
var item = list[i]
var itemKey = getItemKey(item, key)
if (itemKey) {
keyIndex[itemKey] = i
} else {
free.push(item)
}
}
return {
keyIndex: keyIndex,
free: free
}
}
// 获取置顶key的value
function getItemKey (item, key) {
if (!item || !key) return void 666
return typeof key === 'string'
? item[key]
: key(item)
}
exports.makeKeyIndexAndFree = makeKeyIndexAndFree
exports.diffList = diff
原文链接:Nealyang PersonalBlog
前言
众所周知,React中最为人称赞的就是Virtual DOM和 diff 算法的完美结合,让我们可以不顾性能的“任性”更新界面,前面文章中我们有介绍道Virtual DOM,其实就是通过js来模拟dom的实现,然后通过对js obj的操作,最后渲染到页面中,但是,如果当我们修改了一丢丢东西,就要渲染整个页面的话,性能消耗还是非常大的,如何才能准确的修改该修改的地方就是我们diff算法的功能了。
其实所谓的diff算法大概就是当状态发生改变的时候,重新构造一个新的Virtual DOM,然后根据与老的Virtual DOM对比,生成patches补丁,打到对应的需要修改的地方。
这里引用司徒正美的介绍
与传统diff对比
传统的diff算法通过循环递归每一个节点,进行对比,这样的操作效率非常的低,复杂程度O(n^3),其中n标识树的节点总数。如果React仅仅是引入传统的diff算法的话,其实性能也是非常差的。然而FB通过大胆的策略,满足了大多数的性能最大化,将O(n^3)复杂度的问题成功的转换成了O(n),并且后面对于同级节点移动,牺牲一定的DOM操作,算法的复杂度也才打到O(max(M,N))。
实现思路
这里借用下网上的一张图,感觉画的非常赞~
大概解释下:
额。。。其实上面也已近解释了,当Virtual DOM发生变化的时,如上图的第二个和第三个 p 的sonx被删除了,这时候,我们就通过diff算法,计算出前后Virtual DOM的差异->补丁对象patches,然后根据这个patches对象中的信息来遍历之前的老Virtual DOM树,对其需要更新的地方进行更新,使其变成新VIrtual DOM。
diff 策略
Web UI中节点跨级操作特别少,可以忽略不计
拥有相同类的两个组件将会生成相似的树形结构,拥有不同类的两个组件将会生成不同的树形结构。(哪怕一样的而我也认为不一样 -> 大概率优化)
对于同一层级的一组子节点,他们可以通过唯一的key来区分,以方便后续的列表对比算法
基于如上,React分别对tree diff、Component diff 、element diff 进行了算法优化。
tree diff
基于策略一,React的diff非常简单明了:只会对同一层次的节点进行比较。这种非传统的按深度遍历搜索,这种通过大胆假设得到的改进方案,不仅符合实际场景的需要,而且大幅降低了算法实现复杂度,从O(n^3)提升至O(n)。
基于此,React官方并不推荐进行DOM节点的跨层级操作 ,倘若真的出现了,那就是非常消耗性能的remove和create的操作了。
Component diff
由于React是基于组件开发的,所以组件的dom diff其实也非常简单,如果组件是同一类型,则进行tree diff比较。如果不是,则直接放入到patches中。即使是子组件结构类型都相同,只要父组件类型不同,都会被重新渲染。这也说明了为什么我们推荐使用shouldComponentUpdate来提高React性能。
大概的感觉是酱紫的
list diff
对于节点的比较,其实只有三种操作,插入、移动和删除。(这里最麻烦的是移动,后面会介绍实现)。当被diff节点处于同一层级时,通过三种节点操作新旧节点进行更新:插入,移动和删除,同时提供给用户设置key属性的方式调整diff更新中默认的排序方式,在没有key值的列表diff中,只能通过按顺序进行每个元素的对比,更新,插入与删除,在数据量较大的情况下,diff效率低下,如果能够基于设置key标识尽心diff,就能够快速识别新旧列表之间的变化内容,提升diff效率。
对于这三种理论知识可以参照知乎上不可思议的 react diff的介绍。
算法实现
前方高清多码预警
diff
这里引入代码处理我们先撇开list diff中的移动操作,先一步一步去实现
根据节点变更类型,我们定义如下几种变化
解释下index,为了方便演示diff,我们暂时没有想react源码中给每一个Element添加唯一标识
我们遍历每一个VDom,以index为索引。注意这里我们使用全局变量index,因为遍历整个VDom,以index作为区分,所以必须用全局变量,当然,GitHub上有大神的实现方式为
{index:0}
,哈引用类型传递,换汤不换药开始遍历
在diff过程中,我们需要去判断文本标签,需要在util中写一个工具函数
实现思路非常简单,手工流程图了解下
通过diff后,最终我们会拿到新旧VDom的patches补丁,补丁的内容大致如下:
大致是这么个感觉,两秒钟体会下~
这里应该会有点诧异的是
1 3 6...
是什么鬼?因为之前我们说过,diff采用的依旧是深度优先遍历,及时你是改良后的升级产品,但是遍历流程依旧是:
patches
既然patches补丁已经拿到了,该如何使用呢,对,我们依旧是遍历!
Element 调用render后,我们已经可以拿到一个通过VDom(代码)解析后的真是Dom了,所以我们只需要将遍历真实DOM,然后在指定位置修改对应的补丁上指定位置的更改就行了。
代码如下:(自己实现的简易版)
关于setAttrs其实功能都加都明白,这里给个简单实例代码,大家YY下
如上,其实我们已经实现了DOM diff了,但是存在一个问题.
如下图,老集合中包含节点:A、B、C、D,更新后的新集合中包含节点:B、A、D、C,此时新老集合进行 diff 差异化对比,发现 B != A,则创建并插入 B 至新集合,删除老集合 A;以此类推,创建并插入 A、D 和 C,删除 B、C 和 D。
针对这一现象,React 提出优化策略:允许开发者对同一层级的同组子节点,添加唯一 key 进行区分,虽然只是小小的改动,性能上却发生了翻天覆地的变化!
具体介绍可以参照 https://zhuanlan.zhihu.com/p/20346379
这里我们放到代码实现上:
代码参照:list-diff 具体的注释都已经加上。
使用如下:
这里我最困惑的地方时,实现diff都是index为索引,深度优先遍历,如果存在这种移动操作的话,那么之前我补丁patches里记录的index不就没有意义了么??
在 后来在开源的simple-virtual-dom中找到了index作为索引和标识去实现diff的答案。
(leftNode && leftNode.count) ? currentNodeIndex + leftNode.count + 1 : currentNodeIndex + 1
。完整代码如下:话说,这里困扰了我好久好久。。。。
回到开头
也就说明了这段代码的必要性。
0.3中diff的实现
最后我们在看下0.3中diff的实现:
The text was updated successfully, but these errors were encountered: