营销网站建站,宁波外贸公司一览表,carousel wordpress,重庆城乡建设网站首页reconcileChildIterator 和 reconcileChildrenArray 1 #xff09;概述
在react更新某一个节点的时候#xff0c;要根据这个节点#xff0c;它的类型去获取它的children比如说如果是 Function Component#xff0c;它要调用这个 component 计算出它的return的属性return的…reconcileChildIterator 和 reconcileChildrenArray 1 概述
在react更新某一个节点的时候要根据这个节点它的类型去获取它的children比如说如果是 Function Component它要调用这个 component 计算出它的return的属性return的属性可能是一个数组可能是单个的 ReactElement可能是 number, string 这些类型要根据这些不同的类型去进行一些特殊的处理然后得到它的children和对应的 fiber 对象这样的话又可以继续往下去迭代迭代到最终的一个节点在经过这个过程就可以把一个 fiber 树的一侧的子树进行完整的遍历和构建的过程在这个过程当中有一个 children 的类型比较特殊那就是数组在我们使用数组作为children返回的时候都会看到这样的一个提醒那就是我们每一个节点上面必须要有一个key属性如果这个key属性没有那 react 就会给我们一个提醒我们必须要对数组中的每一个节点使用一个唯一的key来标识它这个key和数组调和有特定的关系主要关注: A. key的作用B. 对比数组 children 是否可复用的一个过程C. generator 和 array遍历的区别
2 源码
在 reconcileChildFibers 函数中调用的 reconcileChildrenArray
定位到 packages/react-reconciler/src/ReactChildFiber.js#L732
先看下 reconcileChildrenArray 这个API
新老children的对比过程以及判断节点是否可复用的过程这个过程会涉及到react的一个算法 尽量减少 数组的遍历次数达到复用节点的过程
// 对于 newChildren 的遍历本质上是 O(n)
// 第一次遍历通过 newIdx 的方式后面 特殊情况的判断来加速性能提升
// 只有在最后一个 for 循环中进行完整遍历完成后创建节点
// 这里有很多判断一个节点是否可复用都是通过 key 是否存在来判断的
// 如果不服用并且创建新的 fiber 节点删除老的 fiber 节点 会导致内存申请和内存回收频繁
// 整体对性能存在影响因为过于频繁的内存回收会导致内存抖动的问题
function reconcileChildrenArray(returnFiber: Fiber,currentFirstChild: Fiber | null,newChildren: Array*,expirationTime: ExpirationTime,
): Fiber | null {// This algorithm cant optimize by searching from boths ends since we// dont have backpointers on fibers. Im 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, wed want to optimize for the case// where there are few changes and brute force the comparison instead of// going for the Map. Itd 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 doesnt handle reversal as well as two ended// search but thats unusual. Besides, for the two ended optimization to// work on Iterables, wed need to copy the whole set.// In this first iteration, well 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.if (__DEV__) {// First, validate keys.let knownKeys null;for (let i 0; i newChildren.length; i) {const child newChildren[i];knownKeys warnOnInvalidKey(child, knownKeys);}}// 声明一堆的变量let resultingFirstChild: Fiber | null null;let previousNewFiber: Fiber | null null;let oldFiber currentFirstChild; // 上一次渲染的过程中渲染完成后当前节点的第一个child节点let lastPlacedIndex 0;let newIdx 0;let nextOldFiber null;// 这个for循环以相同的顺序分别遍历新老的children对应的节点判断它的key是否相同// 如果不相同则跳出循环到这个节点为止// 在遍历新老数组的时候找到第一个不能复用的节点这时候就会跳出循环for (; oldFiber ! null newIdx newChildren.length; newIdx) {// 对于react渲染整个数组的过程中会在每个 Fiber 节点上面 设置一个index属性 就是 这个节点在children里的位置// 老的children的Fiber的index newIdx 说明它们的位置不匹配则直接赋值给 nextOldFiberif (oldFiber.index newIdx) {nextOldFiber oldFiber;oldFiber null;} else {// 正常情况下取当前节点的下一个节点nextOldFiber oldFiber.sibling;}const newFiber updateSlot(returnFiber,oldFiber,newChildren[newIdx],expirationTime,);// 为 null 代表 这个节点不能复用 跳出 for循环if (newFiber null) {// TODO: This breaks on empty slots like null children. Thats// 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.// 如果oldFiber 不存在则处理成下一个节点if (oldFiber null) {oldFiber nextOldFiber;}break;}// 存在 newFiber 并且 shouldTrackSideEffectsif (shouldTrackSideEffects) {// newFiber.alternate 不存在说明它没有复用 oldFiber 来产生一个节点而是直接 return 了一个新的Fiber// 如果重新复用的 oldFiber, 那 newFiber.alternate 应该存在的这个节点至少经过一次渲染是有 current 和 workInProgress 的存在的// 没有复用之前的节点则说明 老的节点 失效的状况则删除之if (oldFiber newFiber.alternate null) {// We matched the slot, but we didnt reuse the existing fiber, so we// need to delete the existing child.deleteChild(returnFiber, oldFiber);}}lastPlacedIndex placeChild(newFiber, lastPlacedIndex, newIdx);// 每次循环都会给 previousNewFiber 赋值// 如果没有被赋值代表是新节点if (previousNewFiber null) {// TODO: Move out of the loop. This only happens for the first run.resultingFirstChild newFiber; // 这里} else {// TODO: Defer siblings if were 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 dont want to call updateSlot// with the previous one.previousNewFiber.sibling newFiber; // 如果之前节点已经存在 newFiber 是之前节点previousNewFiber的兄弟节点}previousNewFiber newFiber; // 这里进行赋值oldFiber nextOldFiber; // 接着下一轮}// 跳出循环后当两者相等新数组的children 全部创建fiber对象了, 新数组已经操作完成了if (newIdx newChildren.length) {// Weve reached the end of the new children. We can delete the rest.deleteRemainingChildren(returnFiber, oldFiber); // 对于老数组情况存在 oldFiber删除剩下的节点return resultingFirstChild; // 返回第一个节点第一个节点才是 return fiber 的 child 属性所指向的节点剩下的后续节点都是通过 .sibling 来指向下去的}// oldFiber 为 null 时说明老节点已经被遍历完了if (oldFiber null) {// If we dont have any more existing children we can choose a fast path// since the rest will all be insertions.// 老的节点已经被复用完了新节点还剩下一部分没有创建对剩下的节点都进行创建// 就不需要关心它是否有复用的节点了for (; newIdx newChildren.length; newIdx) {const newFiber createChild(returnFiber,newChildren[newIdx],expirationTime,);if (!newFiber) {continue;}// 同样对这些节点进行 placeChild 操作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; // 存在则处理 sibling 指向}previousNewFiber newFiber;}return resultingFirstChild;}// Add all children to a key map for quick lookups.// 剩下的情况是数组可能存在顺序的变化oldFiber 可能还有一些兄弟节点newChildren 还有几个没有被创建// 从 oldFiber 剩下的节点中找到 newChildren 可以复用的 Fiber 节点// 通过 mapRemainingChildren 来创建一个mapconst existingChildren mapRemainingChildren(returnFiber, oldFiber);// Keep scanning and use the map to restore deleted items as moves.// 最后的遍历把所有节点创建一遍for (; newIdx newChildren.length; newIdx) {// 调用一个 const newFiber updateFromMap(existingChildren,returnFiber,newIdx,newChildren[newIdx],expirationTime,);// 存在 newFiber if (newFiber) {if (shouldTrackSideEffects) {// 存在 current说明已经被复用了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 dont add it to the deletion// list.// 删除 map 里面的删除该匹配的节点existingChildren.delete(newFiber.key null ? newIdx : newFiber.key,);}}// 没有被复用, 执行 placeChildlastPlacedIndex placeChild(newFiber, lastPlacedIndex, newIdx);if (previousNewFiber null) {resultingFirstChild newFiber;} else {previousNewFiber.sibling newFiber;}previousNewFiber newFiber;}}if (shouldTrackSideEffects) {// Any existing children that werent consumed above were deleted. We need// to add them to the deletion list.// 在 existingChildren 中遗留的 fiber 对象执行 删除因为这些fiber对象没有被复用existingChildren.forEach(child deleteChild(returnFiber, child));}return resultingFirstChild;
}下面是上面用到的几个API 进入 updateSlot// 这个方法对比新老的key是否相同来查看它是否可以复用 老的fiber节点
function updateSlot(returnFiber: Fiber,oldFiber: Fiber | null,newChild: any,expirationTime: ExpirationTime,
): Fiber | null {// Update the fiber if the keys match, otherwise return null.// oldFiber 存在的情况下获取它的keyconst key oldFiber ! null ? oldFiber.key : null;// 文本节点没有keyif (typeof newChild string || typeof newChild number) {// Text nodes dont 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;}// 老的节点存在 keyreturn updateTextNode(returnFiber,oldFiber, newChild,expirationTime,);}// 对象类型基于 $$typeof 来判断if (typeof newChild object newChild ! null) {switch (newChild.$$typeof) {case REACT_ELEMENT_TYPE: {// 只有在前后的key相同的情况下才会复用节点if (newChild.key key) {if (newChild.type REACT_FRAGMENT_TYPE) {return updateFragment(returnFiber,oldFiber,newChild.props.children,expirationTime,key,);}return updateElement(returnFiber,oldFiber,newChild,expirationTime,);} else {// 不同则停止不能复用return null;}}// 下面也类似case REACT_PORTAL_TYPE: {if (newChild.key key) {return updatePortal(returnFiber,oldFiber,newChild,expirationTime,);} else {return null;}}}// 继续判断是数组还是可迭代if (isArray(newChild) || getIteratorFn(newChild)) {if (key ! null) {return null;}return updateFragment(returnFiber,oldFiber,newChild,expirationTime,null,);}throwOnInvalidObjectType(returnFiber, newChild);}if (__DEV__) {if (typeof newChild function) {warnOnFunctionType();}}return null;
}进入 placeChildfunction placeChild(newFiber: Fiber,lastPlacedIndex: number,newIndex: number,
): number {// 同步 indexnewFiber.index newIndex;if (!shouldTrackSideEffects) {// Noop.return lastPlacedIndex;}// 获取 currentconst current newFiber.alternate;// 存在 currentif (current ! null) {const oldIndex current.index;if (oldIndex lastPlacedIndex) {// This is a move.newFiber.effectTag Placement; // 代表这个节点要被挂载到 dom上面 顺序变化了说明这个节点被移动了进行 dom 操作tag 就是 Placementreturn lastPlacedIndex;} else {// This item can stay in place.return oldIndex; // 存在于原来的位置}} else {// current 为 null 说明节点没有被渲染过它是一个插入的节点// 插入的节点也是需要使用 Placement 同样进行dom操作// 就是这个节点要根据某个顺序插入到 dom节点的后面// This is an insertion.newFiber.effectTag Placement;return lastPlacedIndex;}
}进入 mapRemainingChildrenfunction mapRemainingChildren(returnFiber: Fiber,currentFirstChild: Fiber,
): Mapstring | number, Fiber {// Add the remaining children to a temporary map so that we can find them by// keys quickly. Implicit (null) keys get added to this set with their index// instead.// 通过 Map 对象找到 key相同的节点判断是否可以复用const existingChildren: Mapstring | number, Fiber new Map();let existingChild currentFirstChild;// 遍历剩下的节点获取其key while (existingChild ! null) {if (existingChild.key ! null) {// set key valueexistingChildren.set(existingChild.key, existingChild);} else {// key 不存在使用 indexexistingChildren.set(existingChild.index, existingChild);}existingChild existingChild.sibling;}return existingChildren;
}进入 updateFromMapfunction updateFromMap(existingChildren: Mapstring | number, Fiber,returnFiber: Fiber,newIdx: number,newChild: any,expirationTime: ExpirationTime,
): Fiber | null {// 匹配 文本节点if (typeof newChild string || typeof newChild number) {// Text nodes dont 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; // 通过 newIdx 来查找不管是否找到// 返回一个text nodereturn updateTextNode(returnFiber,matchedFiber, newChild,expirationTime,);}// 这里和 updateSlot类似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,expirationTime,newChild.key,);}return updateElement(returnFiber,matchedFiber,newChild,expirationTime,);}case REACT_PORTAL_TYPE: {const matchedFiber existingChildren.get(newChild.key null ? newIdx : newChild.key,) || null;return updatePortal(returnFiber,matchedFiber,newChild,expirationTime,);}}if (isArray(newChild) || getIteratorFn(newChild)) {const matchedFiber existingChildren.get(newIdx) || null;return updateFragment(returnFiber,matchedFiber,newChild,expirationTime,null,);}throwOnInvalidObjectType(returnFiber, newChild);}if (__DEV__) {if (typeof newChild function) {warnOnFunctionType();}}return null;
}在 reconcileChildFibers 函数中调用的 reconcileChildrenIterator
定位到 packages/react-reconciler/src/ReactChildFiber.js#L891
function reconcileChildrenIterator(returnFiber: Fiber,currentFirstChild: Fiber | null,newChildrenIterable: Iterable*,expirationTime: ExpirationTime,
): Fiber | null {// This is the same implementation as reconcileChildrenArray(),// but using the iterator instead.// 获取 iteratorFnconst iteratorFn getIteratorFn(newChildrenIterable);invariant(typeof iteratorFn function,An object is not an iterable. This error is likely caused by a bug in React. Please file an issue.,);if (__DEV__) {// We dont support rendering Generators because its a mutation.// See https://github.com/facebook/react/issues/12995if (typeof Symbol function // $FlowFixMe Flow doesnt know about toStringTagnewChildrenIterable[Symbol.toStringTag] Generator) {warning(didWarnAboutGenerators,Using Generators as children is unsupported and will likely yield unexpected results because enumerating a generator mutates it. You may convert it to an array with Array.from() or the [...spread] operator before rendering. Keep in mind you might need to polyfill these features for older browsers.,);didWarnAboutGenerators true;}// Warn about using Maps as childrenif ((newChildrenIterable: any).entries iteratorFn) {warning(didWarnAboutMaps,Using Maps as children is unsupported and will likely yield unexpected results. Convert it to a sequence/iterable of keyed ReactElements instead.,);didWarnAboutMaps true;}// First, validate keys.// Well get a different iterator later for the main pass.// 获取 newChildren这里 newChildrenIterable 是具有 迭代特性的 childrenconst newChildren iteratorFn.call(newChildrenIterable);if (newChildren) {let knownKeys null;let step newChildren.next();for (; !step.done; step newChildren.next()) {const child step.value;knownKeys warnOnInvalidKey(child, knownKeys);}}}const newChildren iteratorFn.call(newChildrenIterable);invariant(newChildren ! null, An iterable object provided no iterator.);// 声明很多变量let resultingFirstChild: Fiber | null null;let previousNewFiber: Fiber | null null;let oldFiber currentFirstChild;let lastPlacedIndex 0;let newIdx 0;let nextOldFiber null;// 通过 next 向下获取新节点let step newChildren.next();// 这个 for 循环的判断是基于迭代器的当遍历结束 .done 返回的是 false// step 都是 next在 Fiber 中的key 如果不存在使用 index// 这里也要模拟一个 index 出来for (;oldFiber ! null !step.done;newIdx, step newChildren.next()) {if (oldFiber.index newIdx) {nextOldFiber oldFiber;oldFiber null;} else {nextOldFiber oldFiber.sibling;}const newFiber updateSlot(returnFiber,oldFiber,step.value,expirationTime,);if (newFiber null) {// TODO: This breaks on empty slots like null children. Thats// 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) {oldFiber nextOldFiber;}break;}if (shouldTrackSideEffects) {if (oldFiber newFiber.alternate null) {// We matched the slot, but we didnt reuse the existing fiber, so we// need to delete the existing child.deleteChild(returnFiber, oldFiber);}}lastPlacedIndex placeChild(newFiber, lastPlacedIndex, newIdx);if (previousNewFiber null) {// TODO: Move out of the loop. This only happens for the first run.resultingFirstChild newFiber;} else {// TODO: Defer siblings if were 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 dont want to call updateSlot// with the previous one.previousNewFiber.sibling newFiber;}previousNewFiber newFiber;oldFiber nextOldFiber;}if (step.done) {// Weve reached the end of the new children. We can delete the rest.deleteRemainingChildren(returnFiber, oldFiber);return resultingFirstChild;}if (oldFiber null) {// If we dont have any more existing children we can choose a fast path// since the rest will all be insertions.for (; !step.done; newIdx, step newChildren.next()) {const newFiber createChild(returnFiber, step.value, expirationTime);if (newFiber null) {continue;}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.const existingChildren mapRemainingChildren(returnFiber, oldFiber);// Keep scanning and use the map to restore deleted items as moves.for (; !step.done; newIdx, step newChildren.next()) {const newFiber updateFromMap(existingChildren,returnFiber,newIdx,step.value,expirationTime,);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 dont add it to the deletion// list.existingChildren.delete(newFiber.key null ? newIdx : newFiber.key,);}}lastPlacedIndex placeChild(newFiber, lastPlacedIndex, newIdx);if (previousNewFiber null) {resultingFirstChild newFiber;} else {previousNewFiber.sibling newFiber;}previousNewFiber newFiber;}}if (shouldTrackSideEffects) {// Any existing children that werent consumed above were deleted. We need// to add them to the deletion list.existingChildren.forEach(child deleteChild(returnFiber, child));}return resultingFirstChild;
}这个 reconcileChildIterator 方法和上面的 reconcileChildrenArray 基本一致就是判断条件不同不再赘述上述两个 API 是 对可遍历的 children 调和的一个过程目的是尽量的复用可复用的 fiber 节点减少对象声明和内存回收的过程