react diff 算法实现思路及原理解析.docx
第
react?diff?算法实现思路及原理解析
目录事例分析diff特点diff思路实现diff算法修改入口文件实现React.Fragment我们需要修改children对比前面几节我们学习了解了react的渲染机制和生命周期,本节我们正式进入基本面试必考的核心地带--diff算法,了解如何优化和复用dom操作的,还有我们常见的key的作用。
diff算法使用在子都是数组的情况下,这点和vue是一样的。如果元素是其他类型的话直接替换就好。
事例分析
按照之前的diff写法,如果元素不同我们是直接删了a再插入的:
按照上面图的结构,我们需要知道那个元素变化了,其实右边相对左边只是把A做了移动,没有dom元素的删除和新增。
diff特点
同级对比On类型不一样销毁老的,创建新的通过key标识
key这里需要标识,主要是为了列表中有删除新增时有优化效果,如果纯静态列表,只是展示作用,key意义不大。
diff思路
使用map存储节点状态,格式如下:
letmap={
keyA:ADOM,
keyB:BDOM
}
定义lastPlacedIndex记录上一个不需要移动的老节点
默认lastPlacedIndex=0,上一个不需要移动的节点,在循环新的子虚拟dom时,如果老节点的挂载索引小于当前值,则改变lastPlacedIndex。这里有点类似vue的最长递增子序列,最大的保证不变的dom元素,只是判断方式不同。
循环新数组
先出A,map中如果有A,表示可以复用
判断A的老挂载索引和lastPlacedIndex对比,如果索引值大,A节点不需要移动,更新lastPlacedIndex的值;否则循环到B,挂载索引小,需要移动B;循环到G,map中没有值,需要新增;新的数组节点循环完,未用到的老节点全部删除。
实现diff算法
修改入口文件
//src/index.js
classCounterextendsReact.Component{
constructor(props){
super(props)
this.state={list:[A,B,C,D,E,F]}
handleClick=()={
this.setState({
list:[A,C,E,B,G]
render(){
//使用空标签
returnReact.Fragment
{this.state.list.map(item={
//这里使用key标识
returnlikey={item}{item}/li
/ul
buttonthis.handleClick}add1/button
/React.Fragment
}
实现React.Fragment
Fragment就是代码片段,不占用dom结构。简写/,对应dom操作为createDocumentFragment。
是用原生库打印,看结构
可以发现就是一个简单的Symbol,所以需要定义新的类型:
为什么一个简单的Symbol可以被渲染成片段呢?依赖于babel解析。
//src/constants.js
exportconstREACT_FRAGMENT=Symbol(react.fragment)//React.Fragment标签
//备用,diff时做patch的type定义
//新的插入
exportconstPLACEMENT=PLACEMENT
//复用的移动
exportconstMOVE=MOVE
在创建元素的时候进行类型判断,记得react.js中导出
//src/react-dom.js
//createDOM方法
elseif(type===REACT_FRAGMENT){
//fragment片段
dom=document.createDocumentFragment()
//updateElement方法
elseif(oldVdom.type===REACT_FRAGMENT){
//fragment不需要对比,直接对比子就可以了
constcur