内容简介:有很多文章讲过react的diff算法,但要么是晦涩难懂的源码分析,让人很难读进去,要么就是流于表面的简单讲解,实际上大家看完后还是一头雾水,因此我将react-lite(基于react v15)中的diff算法实现稍微整理了一下,希望能够帮助大家解惑。在看本文之前,建议先看一下这篇文章,看完后会对react中diff的基本原理有一些理解:对于react diff,我们已知的有两点,一个是会通过key来做比较,另一个是react默认是同级节点做diff,不会考虑到跨层级节点的diff(事实是前端开发中很少有
前言
有很多文章讲过react的diff算法,但要么是晦涩难懂的源码分析,让人很难读进去,要么就是流于表面的简单讲解,实际上大家看完后还是一头雾水,因此我将react-lite(基于react v15)中的diff算法实现稍微整理了一下,希望能够帮助大家解惑。
在看本文之前,建议先看一下这篇文章,看完后会对react中diff的基本原理有一些理解: blog.csdn.net/sexy_squirr…
对于react diff,我们已知的有两点,一个是会通过key来做比较,另一个是react默认是同级节点做diff,不会考虑到跨层级节点的diff(事实是前端开发中很少有DOM节点跨层级移动的)。
递归更新
首先,抛给我们一个问题,那就是react怎么对那么深层次的DOM做的diff?实际上react是对DOM进行递归来做的,遍历所有子节点,对子节点再做递归。
// 超简单代码实现 const compareTwoVnodes(oldVnode, newVnode, dom) { let newNode = dom // 如果新的虚拟DOM是null,那么就将前一次的真实DOM移除掉 if (newVnode == null) { destroyVnode(oldVnode, dom) dom.parentNode.removeChild(dom) } else if (oldVnode.type !== newVnode.type || oldVnode.key !== newVnode.key) { // replace destroyVnode(oldVnode, dom) newNode = initVnode(newVnode, parentContext, dom.namespaceURI) dom.parentNode.replaceChild(newNode, dom) } else if (oldVnode !== newVnode || parentContext) { // same type and same key -> update newNode = updateVNode(oldVnode, newVnode, dom, parentContext) } } /** * 更新虚拟DOM * 这里的type需要注意一下,如果vnode是个html元素,例如h1,那么type就是'h1' * 如果vnode是一个函数组件,例如const Header = () => <h1>header</h1>,那么type就是函数Header * 如果vnode是一个class组件,那么type就是那个class */ const updateVNode = (vnode, node) => { const { type } = vnode; // type是指虚拟DOM的类型 // 如果是class组件 if (type === VCOMPONENT) { return updateComponent(vnode, node) } else (type === VSTATELESS){ return updateStateLess(vnode, node) } updateVChildren(vnode, node) } // 更新class组件(调用render方法拿到新的虚拟DOM) const updateComponent = (vnode, node) => { const { type: Component } = vnode; // type是指虚拟DOM的类型 const newVNode = new Component().render(); compareTwoVnodes(newVNode, vnode, node); } // 更新无状态组件(直接执行函数拿到新的虚拟DOM) const updateStateLess = (vnode, node) => { const { type: Component } = vnode; // type是指虚拟DOM的类型 const newVNode = Component(); compareTwoVnodes(newVNode, vnode, node); } const updateVChildren = (vnode, node) => { for (let i = 0; i < node.children.length; i++) { updateVNode(vnode.children[i], node.children[i]) } } 复制代码
因此,我们这里以其中一层节点来讲解diff是如何做到列表更新的。
状态收集
假设我们的react组件渲染成功后,在浏览器中显示的真实DOM节点是A、B、C、D,我们更新后的虚拟DOM是B、A、E、D。
那我们这里需要做的操作就是,将原来DOM中已经存在的A、B、D进行更新,将原来DOM中存在,而现在不存的C移除掉,再创建新的D节点。
这样一来,问题就简化了很多,我们只需要收集到需要create、remove和update的节点信息就行了。
// oldDoms是真实DOM,newDoms是最新的虚拟DOM const oldDoms = [A, B, C, D], newDoms = [B, A, E, D], updates = [], removes = [], creates = []; // 进行两层遍历,获取到哪些节点需要更新,哪些节点需要移除。 for (let i = 0; i < oldDoms.length; i++) { const oldDom = oldDoms[i] let shouldRemove = true for (let j = 0; j < newDoms.length; j++) { const newDom = newDoms[j]; if ( oldDom.key === newDom.key && oldDom.type === newDom.type ) { updates[j] = { index: j, node: oldDom, parentNode: parentNode // 这里真实DOM的父节点 } shouldRemove = false } } if (shouldRemove) { removes.push({ node: oldDom }) } } // 从虚拟DOM节点来取出不要更新的节点,这就是需要新创建的节点。 for (let j = 0; j < newDoms.length; j++) { if (!updates[j]) { creates.push({ index: j, vnode: newDoms[j], parentNode: parentNode // 这里真实DOM的父节点 }) } } 复制代码
这样,我们便拿到了想要的状态信息。
diff
在得到需要create、update和remove的节点后,我们这时就可以开始进行渲染了。
首先,我们遍历所有需要remove的节点,将其从真实DOM中remove掉。因此这里需要remove掉C节点,最后渲染结果是A、B、D。
const remove = (removes) => { removes.forEach(remove => { const node = remove.node node.parentNode.removeChild(node) }) } 复制代码
其次,我们再遍历需要更新的节点,将其插入到对应的位置中。所以这里最后渲染结果是B、A、D。
const update = (updates) => { updates.forEach(update => { const index = update.index, parentNode = update.parentNode, node = update.node, curNode = parentNode.children[index]; if (curNode !== node) { parentNode.insertBefore(node, curNode) } }) } 复制代码
最后一步,我们需要创建新的DOM节点,并插入到正确的位置中,最后渲染结果为B、A、E、D。
const create = (creates) => { creates.forEach(create => { const index = create.index, parentNode = create.parentNode, vnode = create.vnode, curNode = parentNode.children[index], node = createNode(vnode); // 创建DOM节点 parentNode.insertBefore(node, curNode) }) } 复制代码
虽然这篇文章写的比较简单,但是一个完整的diff流程就是这样了,可以加深对react的一些理解。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- php如何实现session,自己实现session,laravel如何实现session
- AOP如何实现及实现原理
- webpack 实现 HMR 及其实现原理
- Docker实现原理之 - OverlayFS实现原理
- 为什么实现 .NET 的 ICollection 集合时需要实现 SyncRoot 属性?如何正确实现这个属性?
- 自己实现集合框架(十):顺序栈的实现
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。