这一章就来讲讲React
在协调阶段的beginWork
外面次要做的事件 -- dom diff
。
本文次要讲的是React17.0.2
版本的diff
,在此我也画了一个简略的流程图:
reconcileChildren
dom diff
的入口函数就是reconcileChildren
,那么他的源码如下:
//packages/react-reconciler/src/ReactFiberBeginWork.old.jsexport function reconcileChildren( current: Fiber | null,//以后的fiber节点 workInProgress: Fiber,// 新生成的fiber nextChildren: any,// 新生成的reactElement内容 renderLanes: Lanes,//渲染优先级) { if (current === null) { // 如果没有曾经渲染的fiber树,则间接把reactElement内容渲染下来 // If this is a fresh new component that hasn't been rendered yet, we // won't update its child set by applying minimal side-effects. Instead, // we will add them all to the child before it gets rendered. That means // we can optimize this reconciliation pass by not tracking side-effects. workInProgress.child = mountChildFibers( workInProgress, null, nextChildren, renderLanes, ); } else { // If the current child is the same as the work in progress, it means that // we haven't yet started any work on these children. Therefore, we use // the clone algorithm to create a copy of all the current children. // If we had any progressed work already, that is invalid at this point so // let's throw it out. workInProgress.child = reconcileChildFibers( workInProgress, current.child, nextChildren, renderLanes, ); }}
reconcileChildren
的源码并不长,次要做了两件事
- 如果是首次渲染,则会把曾经解决好的
fiber
树进行挂载。 - 如果不是首次渲染则调用
reconcileChildFibers
进行下一步解决。
咱们关注一下mountChildFibers
和reconcileChildFibers
,咱们发现这两个函数别离指向ChildReconciler
,只是mountChildFibers
的参数为false
,reconcileChildFibers
的参数为true
。咱们在这里先埋下一个点,看看这个参数对前期的流程有什么影响。
咱们持续深刻能够发现,ChildReconciler
这个函数冰的执行返回了reconcileChildFibers
,所以这便是reconcileChildren
的外围性能代码所在了。
reconcileChildFibers
function reconcileChildFibers( returnFiber: Fiber, currentFirstChild: Fiber | null, newChild: any, lanes: Lanes, ): Fiber | null { // This function is not recursive. // If the top level item is an array, we treat it as a set of children, // not as a fragment. Nested arrays on the other hand will be treated as // fragment nodes. Recursion happens at the normal flow. // Handle top level unkeyed fragments as if they were arrays. // This leads to an ambiguity between <>{[...]}</> and <>...</>. // We treat the ambiguous cases above the same. const isUnkeyedTopLevelFragment = typeof newChild === 'object' && newChild !== null && newChild.type === REACT_FRAGMENT_TYPE && newChild.key === null; if (isUnkeyedTopLevelFragment) { newChild = newChild.props.children; } // Handle object types const isObject = typeof newChild === 'object' && newChild !== null; // 解决对象类型 if (isObject) { switch (newChild.$$typeof) { // REACT_ELEMENT_TYPE类型 case REACT_ELEMENT_TYPE: return placeSingleChild( reconcileSingleElement( returnFiber, currentFirstChild, newChild, lanes, ), ); // REACT_PORTAL_TYPE类型 case REACT_PORTAL_TYPE: return placeSingleChild( reconcileSinglePortal( returnFiber, currentFirstChild, newChild, lanes, ), ); // REACT_LAZY_TYPE类型 case REACT_LAZY_TYPE: if (enableLazyElements) { const payload = newChild._payload; const init = newChild._init; // TODO: This function is supposed to be non-recursive. return reconcileChildFibers( returnFiber, currentFirstChild, init(payload), lanes, ); } } } // 字符串与数字类型 if (typeof newChild === 'string' || typeof newChild === 'number') { return placeSingleChild( reconcileSingleTextNode( returnFiber, currentFirstChild, '' + newChild, lanes, ), ); } // 数组类型 if (isArray(newChild)) { return reconcileChildrenArray( returnFiber, currentFirstChild, newChild, lanes, ); } // 可迭代的类型 if (getIteratorFn(newChild)) { return reconcileChildrenIterator( returnFiber, currentFirstChild, newChild, lanes, ); } if (isObject) { throwOnInvalidObjectType(returnFiber, newChild); } if (__DEV__) { if (typeof newChild === 'function') { warnOnFunctionType(returnFiber); } } if (typeof newChild === 'undefined' && !isUnkeyedTopLevelFragment) { // If the new child is undefined, and the return fiber is a composite // component, throw an error. If Fiber return types are disabled, // we already threw above. switch (returnFiber.tag) { case ClassComponent: { if (__DEV__) { const instance = returnFiber.stateNode; if (instance.render._isMockFunction) { // We allow auto-mocks to proceed as if they're returning null. break; } } } // Intentionally fall through to the next case, which handles both // functions and classes // eslint-disable-next-lined no-fallthrough case Block: case FunctionComponent: case ForwardRef: case SimpleMemoComponent: { invariant( false, '%s(...): Nothing was returned from render. This usually means a ' + 'return statement is missing. Or, to render nothing, ' + 'return null.', getComponentName(returnFiber.type) || 'Component', ); } } } // Remaining cases are all treated as empty. return deleteRemainingChildren(returnFiber, currentFirstChild); }
reconcileChildFibers
中咱们依据入参newChild
的类型别离对应着不同的解决:
- 当批改的内容为
REACT_ELEMENT_TYPE
类型,调用reconcileSingleElement
函数。 - 当批改的内容为
REACT_PORTAL_TYPE
类型,调用reconcileSinglePortal
函数。 - 当批改的内容为
REACT_LAZY_TYPE
类型,递归调用reconcileChildFibers
函数。 - 当批改的内容问
纯文本
类型,调用reconcileSingleTextNode
函数。 - 当批改的内容为
数组
类型,调用reconcileChildrenArray
函数。 - 当批改的内容为
可迭代
类型,调用reconcileChildrenIterator
函数 - 参考React实战视频解说:进入学习
reconcileSingleElement
reconcileSingleElement
的源码如下:
function reconcileSingleElement( returnFiber: Fiber,// 父级 currentFirstChild: Fiber | null, // 父级下diff的第一个 element: ReactElement, // 以后元素 lanes: Lanes, // 优先级 ): Fiber { const key = element.key; let child = currentFirstChild; while (child !== null) { // TODO: If key === null and child.key === null, then this only applies to // the first item in the list. if (child.key === key) { switch (child.tag) { // 如果为Fragment类型,并且key也相等 case Fragment: { if (element.type === REACT_FRAGMENT_TYPE) { // get前面的兄弟节点增加Deletion标记,用于dom删除 deleteRemainingChildren(returnFiber, child.sibling); // 通过useFiber复用旧fiber与新的props const existing = useFiber(child, element.props.children); existing.return = returnFiber; if (__DEV__) { existing._debugSource = element._source; existing._debugOwner = element._owner; } return existing; } break; } case Block: if (enableBlocksAPI) { let type = element.type; if (type.$$typeof === REACT_LAZY_TYPE) { type = resolveLazyType(type); } if (type.$$typeof === REACT_BLOCK_TYPE) { // The new Block might not be initialized yet. We need to initialize // it in case initializing it turns out it would match. if ( ((type: any): BlockComponent<any, any>)._render === (child.type: BlockComponent<any, any>)._render ) { deleteRemainingChildren(returnFiber, child.sibling); const existing = useFiber(child, element.props); existing.type = type; existing.return = returnFiber; if (__DEV__) { existing._debugSource = element._source; existing._debugOwner = element._owner; } return existing; } } } // We intentionally fallthrough here if enableBlocksAPI is not on. // eslint-disable-next-lined no-fallthrough default: { if ( // 新的ReactElement与旧的current fiber 的key 与 type都雷同 child.elementType === element.type || // Keep this check inline so it only runs on the false path: (__DEV__ ? isCompatibleFamilyForHotReloading(child, element) : false) ) { // 增加标记 deleteRemainingChildren(returnFiber, child.sibling); const existing = useFiber(child, element.props); existing.ref = coerceRef(returnFiber, child, element); existing.return = returnFiber; if (__DEV__) { existing._debugSource = element._source; existing._debugOwner = element._owner; } return existing; } break; } } // 匹配不上,key相等,type不相等,移除旧的fiber以及前面的兄弟 deleteRemainingChildren(returnFiber, child); break; } else { // 如果key不同,则标记Deletion, deleteChild(returnFiber, child); } // 遍历其兄弟 child = child.sibling; } if (element.type === REACT_FRAGMENT_TYPE) { // 如果是fragment类型,创立fragment,并返回。 const created = createFiberFromFragment( element.props.children, returnFiber.mode, lanes, element.key, ); created.return = returnFiber; return created; } else { //如果不是fragment,创立element并返回fiber const created = createFiberFromElement(element, returnFiber.mode, lanes); created.ref = coerceRef(returnFiber, currentFirstChild, element); created.return = returnFiber; return created; } }
依据源码,reconcileSingleElement
函数中会遍历以后父级fiber
上面的所有子fiber
,依据旧的fiber
与新生成的ReactElement的key
和type
进行比拟:
- 如果旧的
fiber
子节点与新的子节点的key
和type
不统一,给以后的旧的fiber
子节点增加上Deletion
标记,持续遍历其兄弟节点。 - 如果旧的
fiber
子节点与新的子节点的key
是统一的,就会依据以后的节点类型去做匹配解决,通过deleteRemainingChildren
给以后子节点以及前面的所有的兄弟节点增加上Deletion
标记,并且通过useFiber
复用该子节点和该子节点新的props
。 - 如果旧的
fiber
子节点与新的子节点的类型匹配不上,则会间接给旧的fiber
子节点打上Deletion
标记,移除子节点以及前面的所有兄弟节点。 - 如果旧的
fiber
树遍历结束,然而发现还没有匹配完的节点,那么会通过createFiberFromFragment
,createFiberFromElement
创立新的fiber
节点,并指向父级fiber
。
reconcileSingPortal
function reconcileSinglePortal( returnFiber: Fiber, currentFirstChild: Fiber | null, portal: ReactPortal, lanes: Lanes, ): Fiber { const key = portal.key; let child = currentFirstChild; while (child !== null) { // TODO: If key === null and child.key === null, then this only applies to // the first item in the list. if (child.key === key) { if ( child.tag === HostPortal && child.stateNode.containerInfo === portal.containerInfo && child.stateNode.implementation === portal.implementation ) { deleteRemainingChildren(returnFiber, child.sibling); const existing = useFiber(child, portal.children || []); existing.return = returnFiber; return existing; } else { deleteRemainingChildren(returnFiber, child); break; } } else { deleteChild(returnFiber, child); } child = child.sibling; } const created = createFiberFromPortal(portal, returnFiber.mode, lanes); created.return = returnFiber; return created; }
有了下面REACT_ELEMENT_TYPE
的解说,对于REACT_PORTAL_TYPE
的源码就有肯定的思路了,如果还不晓得ReactPortal
的作用
placeSingleChild
上述的不论是REACT_ELEMENT_TYPE
、REACT_PORTAL_TYPE
、REACT_LAZY_TYPE
都是用了placeSingleChild
包裹起来的,咱们来看一看他做了什么事件。
function placeSingleChild(newFiber: Fiber): Fiber { // This is simpler for the single child case. We only need to do a // placement for inserting new children. if (shouldTrackSideEffects && newFiber.alternate === null) { newFiber.flags = Placement; } return newFiber; }
那么这里咱们就发现了这个shouldTrackSideEffects
,还记得咱们在后面讲的ChildReconciler
函数的入参吗?他只是一个布尔。在挂载阶段shouldTrackSideEffects:false
,间接是return newFiber
。不必要的标记减少性能开销。而在更新阶段,就必须要做这样的操作。Placement
为dom更新时的插入标记。
reconcileSingleTextNode
reconcileSingleTextNode
的源码如下:
function reconcileSingleTextNode( returnFiber: Fiber, currentFirstChild: Fiber | null, textContent: string, lanes: Lanes, ): Fiber { // There's no need to check for keys on text nodes since we don't have a // way to define them. //第一个子节点为文本类型 if (currentFirstChild !== null && currentFirstChild.tag === HostText) { // We already have an existing node so let's just update it and delete // the rest. deleteRemainingChildren(returnFiber, currentFirstChild.sibling); const existing = useFiber(currentFirstChild, textContent); existing.return = returnFiber; return existing; } // The existing first child is not a text node so we need to create one // and delete the existing ones. //非文本类型打上标记,创立新的文本类型节点 deleteRemainingChildren(returnFiber, currentFirstChild); const created = createFiberFromText(textContent, returnFiber.mode, lanes); created.return = returnFiber;//指向父级 return created; }
- 如果以后
fiber
的第一个子节点的类型为文本类型
,那么其所有的兄弟节点增加Deletion
标记,通过useFiber
复用以后fiber
的子节点和textContent
,并指向父级fiber
。 - 如果不为文本类型,那么给旧的节点增加
Deletion
标记,通过createFiberFromText
创立新的文本类型节点,并指向父级fiber
。
reconcileChildrenArray
下面的状况为一对一
或者多对一
的状况,那么如果是一对多
或者多对多
的状况就要用reconcileChildrenArray来解决了。
function reconcileChildrenArray( returnFiber: Fiber, currentFirstChild: Fiber | null, newChildren: Array<*>, lanes: Lanes, ): Fiber | null { // This algorithm can't optimize by searching from both ends since we // don't have backpointers on fibers. I'm trying to see how far we can get // with that model. If it ends up not being worth the tradeoffs, we can // add it later. // Even with a two ended optimization, we'd want to optimize for the case // where there are few changes and brute force the comparison instead of // going for the Map. It'd like to explore hitting that path first in // forward-only mode and only go for the Map once we notice that we need // lots of look ahead. This doesn't handle reversal as well as two ended // search but that's unusual. Besides, for the two ended optimization to // work on Iterables, we'd need to copy the whole set. // In this first iteration, we'll just live with hitting the bad case // (adding everything to a Map) in for every insert/move. // If you change this code, also update reconcileChildrenIterator() which // uses the same algorithm. // 验证key是否非法 if (__DEV__) { // First, validate keys. let knownKeys = null; for (let i = 0; i < newChildren.length; i++) { const child = newChildren[i]; knownKeys = warnOnInvalidKey(child, knownKeys, returnFiber); } } // 要返回的第一个子fiber节点 let resultingFirstChild: Fiber | null = null; let previousNewFiber: Fiber | null = null; let oldFiber = currentFirstChild; let lastPlacedIndex = 0; let newIdx = 0; let nextOldFiber = null; // 解决更新状况 // 依据 oldFiber 的 index 和 newChildren 的下标,找到要比照更新的 oldFiber for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) { if (oldFiber.index > newIdx) { nextOldFiber = oldFiber; oldFiber = null; } else { nextOldFiber = oldFiber.sibling; } // 通过updateSlot来diff老的和新的子fiber节点,生成新的fiber const newFiber = updateSlot( returnFiber, oldFiber, newChildren[newIdx], lanes, ); // 如果为null则阐明不可复用,退出第一轮循环 if (newFiber === null) { // TODO: This breaks on empty slots like null children. That's // unfortunate because it triggers the slow path all the time. We need // a better way to communicate whether this was a miss or null, // boolean, undefined, etc. if (oldFiber === null) { oldFiber = nextOldFiber; } break; } if (shouldTrackSideEffects) { if (oldFiber && newFiber.alternate === null) { // We matched the slot, but we didn't reuse the existing fiber, so we // need to delete the existing child. deleteChild(returnFiber, oldFiber); } } // 记录老的fiber的下标,并打上PlaceMent标记 lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx); if (previousNewFiber === null) { // TODO: Move out of the loop. This only happens for the first run. // 如果以后节点的上一个节点是null,则示意以后节点为第一个节点,要返回进来 resultingFirstChild = newFiber; } else { // TODO: Defer siblings if we're not at the right index for this slot. // I.e. if we had null values before, then we want to defer this // for each null value. However, we also don't want to call updateSlot // with the previous one. // 如果不是则阐明不是第一个节点,须要持续解决其兄弟节点 previousNewFiber.sibling = newFiber; } previousNewFiber = newFiber; oldFiber = nextOldFiber; } if (newIdx === newChildren.length) { // We've reached the end of the new children. We can delete the rest. // 如果newChildren遍历完了,则须要删除前面的所有旧的fiber,打上Deletion标记 deleteRemainingChildren(returnFiber, oldFiber); return resultingFirstChild; } if (oldFiber === null) { // If we don't have any more existing children we can choose a fast path // since the rest will all be insertions. // 如果旧的遍历完了,新的还有那么这都是新增的,通过createChild创立新的节点 for (; newIdx < newChildren.length; newIdx++) { const newFiber = createChild(returnFiber, newChildren[newIdx], lanes); if (newFiber === null) { continue; } // 解决挪动的状况,给挪动的节点加上新增标记,插入到fiber链表树当中 lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx); if (previousNewFiber === null) { // TODO: Move out of the loop. This only happens for the first run. resultingFirstChild = newFiber; } else { previousNewFiber.sibling = newFiber; } previousNewFiber = newFiber; } return resultingFirstChild; } // Add all children to a key map for quick lookups. // 如果新的老的都没有遍历结束,则须要解决成一个map,老的key作为key,新的value作为value const existingChildren = mapRemainingChildren(returnFiber, oldFiber); // Keep scanning and use the map to restore deleted items as moves. // 遍历剩下的newChildren for (; newIdx < newChildren.length; newIdx++) { // 找到mapRemainingChildren中key相等的fiber,复用fiber节点并更新props const newFiber = updateFromMap( existingChildren, returnFiber, newIdx, newChildren[newIdx], lanes, ); if (newFiber !== null) { if (shouldTrackSideEffects) { if (newFiber.alternate !== null) { // The new fiber is a work in progress, but if there exists a // current, that means that we reused the fiber. We need to delete // it from the child list so that we don't add it to the deletion // list. // 如果以后的心得节点的指针为null,则须要删除老的节点 existingChildren.delete( newFiber.key === null ? newIdx : newFiber.key, ); } } // 解决挪动dom状况,记录index并打上PlaceMent标记 lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx); // 将新创建的fiber插入到fiber链表树当中 if (previousNewFiber === null) { resultingFirstChild = newFiber; } else { previousNewFiber.sibling = newFiber; } previousNewFiber = newFiber; } } if (shouldTrackSideEffects) { // Any existing children that weren't consumed above were deleted. We need // to add them to the deletion list. //删除掉残余的fiber节点 existingChildren.forEach(child => deleteChild(returnFiber, child)); } return resultingFirstChild; }
既然是多对多的这样的一个更新场景,那么就会呈现节点的减少、缩小、挪动等状况,因为大部分的理论场景中,节点更新的状况,往往比减少、缩小多得多,所以React优先解决了更新的状况。比照的对象为旧的fiber和newChildren。
首先先对newChildren
进行遍历,将以后的 oldFiber 与 以后 newIdx 下标的 newChild 通过 updateSlot
进行 diff。
updateSlot
function updateSlot( returnFiber: Fiber, oldFiber: Fiber | null, newChild: any, lanes: Lanes, ): Fiber | null { // Update the fiber if the keys match, otherwise return null. const key = oldFiber !== null ? oldFiber.key : null; // 如果是纯文本 if (typeof newChild === 'string' || typeof newChild === 'number') { // Text nodes don't have keys. If the previous node is implicitly keyed // we can continue to replace it without aborting even if it is not a text // node. if (key !== null) { return null; } return updateTextNode(returnFiber, oldFiber, '' + newChild, lanes); } // 如果是对象 if (typeof newChild === 'object' && newChild !== null) { switch (newChild.$$typeof) { case REACT_ELEMENT_TYPE: { if (newChild.key === key) { if (newChild.type === REACT_FRAGMENT_TYPE) { return updateFragment( returnFiber, oldFiber, newChild.props.children, lanes, key, ); } return updateElement(returnFiber, oldFiber, newChild, lanes); } else { return null; } } case REACT_PORTAL_TYPE: { if (newChild.key === key) { return updatePortal(returnFiber, oldFiber, newChild, lanes); } else { return null; } } case REACT_LAZY_TYPE: { if (enableLazyElements) { const payload = newChild._payload; const init = newChild._init; return updateSlot(returnFiber, oldFiber, init(payload), lanes); } } } // 如果是数组或者可迭代的 if (isArray(newChild) || getIteratorFn(newChild)) { if (key !== null) { return null; } return updateFragment(returnFiber, oldFiber, newChild, lanes, null); } throwOnInvalidObjectType(returnFiber, newChild); } if (__DEV__) { if (typeof newChild === 'function') { warnOnFunctionType(returnFiber); } } return null; }
可见updateSlot
函数解决与下面的单节点解决相似:
- 当
oldFiber
与newChildren[newIdx]
的key
、type
雷同,则阐明是能够复用的,依据oldFiber
和newChild
的props
生成新的fiber
,通过placeChild
给新生成的fiber
打上Placement
副作用标记,同时新 fiber 与之前遍历生成的新 fiber 构建链表树关系。而后继续执行遍历,对下一个oldFiber
和下一个newIdx
下标的newFiber
继续执行diff
- 当
oldFiber
与newChildren[newIdx]
的key
或type
不雷同,阐明不可复用,返回null,间接跳出遍历。
第一轮遍历完结后,可能会执行以下几种状况:
- 若
newChildren
遍历完了,那剩下的oldFiber
都是待删除的,通过deleteRemainingChildren
对剩下的oldFiber
打上Deletion
副作用标记。 - 若
oldFiber
遍历完了,那剩下的newChildren
都是须要新增的,遍历剩下的newChildren
,通过createChild
创立新的fiber
,placeChild
给新生成的fiber
打上Placement
副作用标记并增加到fiber
链表树中。 - 若
oldFiber
和newChildren
都未遍历完,通过mapRemainingChildren
创立一个以剩下的oldFiber
的key
为key
`oldFiber为
value的
map。而后对剩下的
newChildren进行遍历,通过
updateFromMap在
map中寻找具备雷同
key创立新的
fiber(若找到则基于 oldFiber 和 newChild 的 props创立,否则间接基于 newChild 创立),则从
map中删除以后的
key,而后
placeChild给新生成的
fiber打上
Placement副作用标记并增加到
fiber链表树中。遍历完之后则
existingChildren还剩下
oldFiber的话,则都是待删除的 fiber,
deleteChild对其打上
Deletion` 副作用标记。
- 若
updateFromMap
function updateFromMap( existingChildren: Map<string | number, Fiber>, returnFiber: Fiber, newIdx: number, newChild: any, lanes: Lanes, ): Fiber | null { if (typeof newChild === 'string' || typeof newChild === 'number') { // Text nodes don't have keys, so we neither have to check the old nor // new node for the key. If both are text nodes, they match. const matchedFiber = existingChildren.get(newIdx) || null; return updateTextNode(returnFiber, matchedFiber, '' + newChild, lanes); } if (typeof newChild === 'object' && newChild !== null) { switch (newChild.$$typeof) { case REACT_ELEMENT_TYPE: { const matchedFiber = existingChildren.get( newChild.key === null ? newIdx : newChild.key, ) || null; if (newChild.type === REACT_FRAGMENT_TYPE) { return updateFragment( returnFiber, matchedFiber, newChild.props.children, lanes, newChild.key, ); } return updateElement(returnFiber, matchedFiber, newChild, lanes); } case REACT_PORTAL_TYPE: { const matchedFiber = existingChildren.get( newChild.key === null ? newIdx : newChild.key, ) || null; return updatePortal(returnFiber, matchedFiber, newChild, lanes); } case REACT_LAZY_TYPE: if (enableLazyElements) { const payload = newChild._payload; const init = newChild._init; return updateFromMap( existingChildren, returnFiber, newIdx, init(payload), lanes, ); } } if (isArray(newChild) || getIteratorFn(newChild)) { const matchedFiber = existingChildren.get(newIdx) || null; return updateFragment(returnFiber, matchedFiber, newChild, lanes, null); } throwOnInvalidObjectType(returnFiber, newChild); } if (__DEV__) { if (typeof newChild === 'function') { warnOnFunctionType(returnFiber); } } return null; }
updateFromMap
与下面的函数逻辑相似,不再复述,reconcileChildrenArray
的流程如下。
React的diff策略
- 传统的diff算法的工夫复杂度为
O(n³)
,是因为这种算法是以一棵树的一个节点比照另一棵树的所有节点的,这里为O(n²)
,之后还须要再解决一次新生成的dom
树,故而O(n³)
是这么算进去的。 - 古代的diff算法的工夫复杂度为O(n),他是怎么算进去的呢?原来它采纳的是,
深度优先
、同层比拟
。就拿当初的MVVM
框架来说吧,借助了vdom
这样的一个概念,同层比拟在于比拟同一层的节点元素,不会呈现不同层之间比拟的状况。
上图为一般的两棵树,用来论述什么叫同层级
比拟。
react
中的diff
策略,则体现为tree diff
、component diff
、element diff
。tree diff
:
如果把上图的dom
树当做是current Fiber
和workInProgress Fiber
,那么从左到右的操作将会是- 在
C
节点上面删除G
节点。 - 在
A
节点上面创立W
节点。 - 在
E
节点上面删除J
节点。 - 在
F
上面创立J
节点。 component diff
:组件之间的比拟,只会比拟他们的类型,如果上图右边的B
节点的类型为div
,左边的B
节点类型为p
,那么示意此节点不可复用,则进行的操作如下- 在
C
节点上面删除G
节点。 - 在
A
节点上面创立W
节点。 - 在
root
上面创立B
节点。 - 在
B
节点上面创立E
节点。 - 在
E
节点上面创立I
节点。 - 在
E
节点上面删除J
节点。 - 在
B
几点上面创立F
节点。 - 在
F
节点上面创立J
节点。 - 删除老的
B
节点。 element diff
:元素之间的比拟分为挪动
、删除
、新增
,如果是上面的这样的例子,他将会进行这些操作。- 删除
A
节点。 - 挪动
E
节点到C
节点之后。 - 创立
J
节点插入到D
节点之后。 - 删除
F
节点。
总结
这一章讲述了,react
的diff
过程,也学习了react
的diff
策略,通过上述的解决之后就会走到completeUnitWork
,在这个过程中咱们会依据新生成的fiber
树去创立dom
元素,依据其上的副作用flags
、effectLists
链表去做副作用的解决,在commit
阶段的commitMutationEffects
函数中进行实在dom
的插入解决,下一章将讲述实在dom
的生成