reconcileChildren
1 )概述
- 在更新了一个节点之后,拿到它的props.children
- 要根据这个children里面的 ReactElement 来去创建子树的所有的 fiber 对象
- 要根据 props.children 来生成 fiber 子树,然后判断 fiber 对象它是否是可以复用的
- 因为我们在第一次渲染的时候,就已经渲染了整个 fiber 子树
- 再有一个更新进来之后,state 变化可能会导致一些子节点产生一个新的变化
- 可能就不能复用之前的 fiber 节点了,它里面的很多东西都变得不一样
- 大部分情况下所有 fiber 节点都是可以可以重复利用的
- 这个时候我们根据什么进行判断,是这里面的一个非常重要的一个点
- 在这里就会拿出react当中的非常重要的key,列表根据key来优化
2 )源码
在 packages/react-reconciler/src/ReactFiberBeginWork.js#L137 中
export function reconcileChildren(
current: Fiber | null,
workInProgress: Fiber,
nextChildren: any,
renderExpirationTime: ExpirationTime,
) {
if (current === null) {
// 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,
renderExpirationTime,
);
} 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,
renderExpirationTime,
);
}
}
- 可见,根据 current 是否为 null 调用两个方法:
mountChildFibers
,reconcileChildFibers
- 两个方法的区别,在第二个参数
- 在前者方法中传递的是 null, 因为 第一次渲染,不存在 current.child
- 因为第一次渲染的时候,都是父亲节点先渲染,子节点后渲染
- 只有在后续的渲染过程当中,已经过第一次渲染了我们的父节点
- 这个时候才有一个子节点存在,所以这是一个区别
- 在 react-reconciler/src/ReactFiberBeginWork.js 中 的 reconcileChildren 调用这个
reconcileChildFibers
- 看一下这两个方法,它们来自于 ReactChildFiber.js
export const reconcileChildFibers = ChildReconciler(true); export const mountChildFibers = ChildReconciler(false);
- 可见,上述两个方法的区别是 参数的 true 和 false
- 这个参数的意思是: 表示是否要跟踪副作用
- 进入 ChildReconciler,这个方法 1k 多行,这里不去全部摘抄
- 用到哪里,逐个分析
整体框架
// 参数 shouldTrackSideEffects 表示是否要跟踪副作用
function ChildReconciler(shouldTrackSideEffects) {
// ... 跳过很多代码
// This API will tag the children with the side-effect of the reconciliation
// itself. They will be added to the side-effect list as we pass through the
// children and the parent.
function reconcileChildFibers(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
newChild: any,
expirationTime: ExpirationTime,
): Fiber | null {
// 省略很多代码
// Remaining cases are all treated as empty.
return deleteRemainingChildren(returnFiber, currentFirstChild);
}
return reconcileChildFibers;
}
-
在
ChildReconciler
方法的最后,return reconcileChildFibers;
,先看下这个方法// This API will tag the children with the side-effect of the reconciliation // itself. They will be added to the side-effect list as we pass through the // children and the parent. function reconcileChildFibers( returnFiber: Fiber, currentFirstChild: Fiber | null, newChild: any, expirationTime: ExpirationTime, ): 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. // newChild 是计算出来的 新children // 这个节点是一个 top level 的节点,因为 <></> 这种类型会匹配 REACT_FRAGMENT_TYPE // 符合以下就是没有key的 topLevel节点 const isUnkeyedTopLevelFragment = typeof newChild === 'object' && newChild !== null && newChild.type === REACT_FRAGMENT_TYPE && newChild.key === null; // 如果匹配了,那么 Fragment 是没有任何的操作更新的, 本身并没有什么实际的意义 // 如果匹配了,则将newChild赋值 if (isUnkeyedTopLevelFragment) { newChild = newChild.props.children; } // Handle object types const isObject = typeof newChild === 'object' && newChild !== null; // 如果是对象,说明有下面两种情况 // 下面两种方法调用相似,都是基于 placeSingleChild 传递不同参数 // 一个是 reconcileSingleElement // 另一个是 reconcileSinglePortal if (isObject) { switch (newChild.$$typeof) { // 匹配 REACT_ELEMENT,是通过 React.createElement 产生的 case REACT_ELEMENT_TYPE: return placeSingleChild( reconcileSingleElement( returnFiber, currentFirstChild, newChild, expirationTime, ), ); // 匹配 REACT_PORTAL, 是通过 ReactDOM.createPortal case REACT_PORTAL_TYPE: return placeSingleChild( reconcileSinglePortal( returnFiber, currentFirstChild, newChild, expirationTime, ), ); } } // 匹配到 string 和 number 类就是 text类型的Node if (typeof newChild === 'string' || typeof newChild === 'number') { return placeSingleChild( reconcileSingleTextNode( returnFiber, currentFirstChild, '' + newChild, expirationTime, ), ); } // 匹配到数组 if (isArray(newChild)) { return reconcileChildrenArray( returnFiber, currentFirstChild, newChild, expirationTime, ); } // 匹配到可迭代的对象 if (getIteratorFn(newChild)) { return reconcileChildrenIterator( returnFiber, currentFirstChild, newChild, expirationTime, ); } // 以上都没有匹配,但仍然是对象,则 throw error if (isObject) { throwOnInvalidObjectType(returnFiber, newChild); } // 忽略 DEV if (__DEV__) { if (typeof newChild === 'function') { warnOnFunctionType(); } } // 匹配到 undefined 的对象 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) { // 匹配到 ClassComponent 忽略 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 // 匹配到 FunctionComponent 提醒 case FunctionComponent: { const Component = returnFiber.type; invariant( false, '%s(...): Nothing was returned from render. This usually means a ' + 'return statement is missing. Or, to render nothing, ' + 'return null.', Component.displayName || Component.name || 'Component', ); } } } // Remaining cases are all treated as empty. // 以上都不符合,则 是 null, 删除现有所有 children // 新的 props.children 都是 null, 老的 props 节点都应该被删除 return deleteRemainingChildren(returnFiber, currentFirstChild); }
-
接下来看不同节点的更新
reconcileSingleElement
// 从当前已有的所有子节点中,找到一个可以复用新的子节点的Fiber对象 // 复用它之后,把剩下兄弟节点全部删掉 function reconcileSingleElement( returnFiber: Fiber, currentFirstChild: Fiber | null, // 是当前已有的fiber节点,初次渲染没有,后续渲染后很可能存在 element: ReactElement, expirationTime: ExpirationTime, ): Fiber { // 获取 key 和 child const key = element.key; let child = currentFirstChild; // child 存在 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 === Fragment ? element.type === REACT_FRAGMENT_TYPE // 是否是 frament : child.elementType === element.type // 新老 element type ) { // 删除 已存在当前节点的兄弟节点 // 为何要删除兄弟节点,因为我们这次渲染只有一个节点,老的节点有兄弟节点,新的节点只有一个,删除之 deleteRemainingChildren(returnFiber, child.sibling); const existing = useFiber( child, element.type === REACT_FRAGMENT_TYPE ? element.props.children : element.props, expirationTime, ); // 挂载属性 existing.ref = coerceRef(returnFiber, child, element); existing.return = returnFiber; if (__DEV__) { existing._debugSource = element._source; existing._debugOwner = element._owner; } // 找到可复用节点,直接return return existing; } else { // 条件不符合,删除节点并退出 deleteRemainingChildren(returnFiber, child); break; } } else { // key 不同,则删除 deleteChild(returnFiber, child); } // 当前兄弟节点 等于 child 再次进入while循环 child = child.sibling; } // 创建新的节点, 基于各种不同的类型,调用不同的创建 Fiber的方式 if (element.type === REACT_FRAGMENT_TYPE) { const created = createFiberFromFragment( element.props.children, // 注意这个参数 returnFiber.mode, expirationTime, element.key, ); created.return = returnFiber; return created; } else { const created = createFiberFromElement( element, returnFiber.mode, expirationTime, ); created.ref = coerceRef(returnFiber, currentFirstChild, element); created.return = returnFiber; return created; } }
- 进入
createFiberFromFragment
export function createFiberFromFragment( elements: ReactFragment, mode: TypeOfMode, expirationTime: ExpirationTime, key: null | string, ): Fiber { const fiber = createFiber(Fragment, elements, key, mode); fiber.expirationTime = expirationTime; return fiber; } // This is a constructor function, rather than a POJO constructor, still // please ensure we do the following: // 1) Nobody should add any instance methods on this. Instance methods can be // more difficult to predict when they get optimized and they are almost // never inlined properly in static compilers. // 2) Nobody should rely on `instanceof Fiber` for type testing. We should // always know when it is a fiber. // 3) We might want to experiment with using numeric keys since they are easier // to optimize in a non-JIT environment. // 4) We can easily go from a constructor to a createFiber object literal if that // is faster. // 5) It should be easy to port this to a C struct and keep a C implementation // compatible. const createFiber = function( tag: WorkTag, pendingProps: mixed, // 对于 fragment来说,props只有children, 直接把children作为props key: null | string, mode: TypeOfMode, ): Fiber { // $FlowFixMe: the shapes are exact here but Flow doesn't like constructors return new FiberNode(tag, pendingProps, key, mode); };
- 以上的 fragment 可以在 ReactFiberBeginWork.js 中的 updateFragment 方法中找到验证
function updateFragment( current: Fiber | null, workInProgress: Fiber, renderExpirationTime: ExpirationTime, ) { // 看这里,直接通过 .pendingProps 而非 props.children 获取,参考上述 createFiber 写明原因 const nextChildren = workInProgress.pendingProps; reconcileChildren( current, workInProgress, nextChildren, renderExpirationTime, ); return workInProgress.child; }
- 进入
createFiberFromElement
export function createFiberFromElement( element: ReactElement, mode: TypeOfMode, expirationTime: ExpirationTime, ): Fiber { let owner = null; if (__DEV__) { owner = element._owner; } // 获取各类属性 const type = element.type; const key = element.key; const pendingProps = element.props; // 创建 Fiber const fiber = createFiberFromTypeAndProps( type, key, pendingProps, owner, mode, expirationTime, ); if (__DEV__) { fiber._debugSource = element._source; fiber._debugOwner = element._owner; } return fiber; }
- 进入
createFiberFromTypeAndProps
// 这个方法比较复杂,要判断不同的 type 给 fiber 对象增加 不同的 tag // 主要是去匹配 fiberTag export function createFiberFromTypeAndProps( type: any, // React$ElementType key: null | string, pendingProps: any, owner: null | Fiber, mode: TypeOfMode, expirationTime: ExpirationTime, ): Fiber { let fiber; // 组件tag未知时的初始化配置 未指定状态 let fiberTag = IndeterminateComponent; // The resolved type is set if we know what the final type will be. I.e. it's not lazy. let resolvedType = type; // 根据不同类型来处理 fiberTag if (typeof type === 'function') { // 判断是否有 constructor 方法 /* function shouldConstruct(Component: Function) { const prototype = Component.prototype; // 注意,这里 isReactComponent 是一个 {} return !!(prototype && prototype.isReactComponent); } */ if (shouldConstruct(type)) { fiberTag = ClassComponent; } } else if (typeof type === 'string') { fiberTag = HostComponent; } else { getTag: switch (type) { case REACT_FRAGMENT_TYPE: return createFiberFromFragment( pendingProps.children, mode, expirationTime, key, ); case REACT_CONCURRENT_MODE_TYPE: return createFiberFromMode( pendingProps, mode | ConcurrentMode | StrictMode, expirationTime, key, ); case REACT_STRICT_MODE_TYPE: return createFiberFromMode( pendingProps, mode | StrictMode, expirationTime, key, ); case REACT_PROFILER_TYPE: return createFiberFromProfiler(pendingProps, mode, expirationTime, key); case REACT_SUSPENSE_TYPE: return createFiberFromSuspense(pendingProps, mode, expirationTime, key); default: { if (typeof type === 'object' && type !== null) { switch (type.$$typeof) { case REACT_PROVIDER_TYPE: fiberTag = ContextProvider; break getTag; case REACT_CONTEXT_TYPE: // This is a consumer fiberTag = ContextConsumer; break getTag; case REACT_FORWARD_REF_TYPE: fiberTag = ForwardRef; break getTag; case REACT_MEMO_TYPE: fiberTag = MemoComponent; break getTag; case REACT_LAZY_TYPE: fiberTag = LazyComponent; resolvedType = null; break getTag; } } let info = ''; if (__DEV__) { if ( type === undefined || (typeof type === 'object' && type !== null && Object.keys(type).length === 0) ) { info += ' You likely forgot to export your component from the file ' + "it's defined in, or you might have mixed up default and " + 'named imports.'; } const ownerName = owner ? getComponentName(owner.type) : null; if (ownerName) { info += '\n\nCheck the render method of `' + ownerName + '`.'; } } invariant( false, 'Element type is invalid: expected a string (for built-in ' + 'components) or a class/function (for composite components) ' + 'but got: %s.%s', type == null ? type : typeof type, info, ); } } } // 最终创建 Fiber fiber = createFiber(fiberTag, pendingProps, key, mode); fiber.elementType = type; fiber.type = resolvedType; fiber.expirationTime = expirationTime; return fiber; }
- 以上,
reconcileSingleElement
就完了
- 进入
-
进入
reconcileSingleTextNode
function reconcileSingleTextNode( returnFiber: Fiber, currentFirstChild: Fiber | null, textContent: string, expirationTime: ExpirationTime, ): 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); // 基于 useFiber 复用当前节点 const existing = useFiber(currentFirstChild, textContent, expirationTime); 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, expirationTime, ); created.return = returnFiber; return created; }
- 这个流程就比较简单了
-
现在我们主要看下
deleteRemainingChildren
这个apifunction deleteRemainingChildren( returnFiber: Fiber, // 当前正在更新的节点 currentFirstChild: Fiber | null, // 子节点 ): null { // 这是第一次渲染的时候,没有子节点 // 直接 return if (!shouldTrackSideEffects) { // Noop. return null; } // TODO: For the shouldClone case, this could be micro-optimized a bit by // assuming that after the first child we've already added everything. // 一个个的删除 let childToDelete = currentFirstChild; while (childToDelete !== null) { deleteChild(returnFiber, childToDelete); childToDelete = childToDelete.sibling; } return null; }
- 进入
deleteChild
// 可以看到这里没有实施删除的操作,只是改变了节点上的 effectTag // 这里我们只更新 Fiber Tree, 如果真的要 delete 就会影响到 dom节点 // 所以删除的流程不在这里做,只有在下个阶段,也就是 commit 阶段来做 // 就是把Fiber Tree 需要更新的流程都过一遍之后,把整个需要更新的属性 // 通过 firstEffect,lastEffect 这种链条,最终链到根节点上面 // 最终要执行更新 只需要在 commit 阶段拿到 根节点上面的 这种链条把每个节点去执行即可 // 在处理更新的时候任务是可以中断的,但是在 commit 阶段任务是不可中断的 function deleteChild(returnFiber: Fiber, childToDelete: Fiber): void { if (!shouldTrackSideEffects) { // Noop. return; } // Deletions are added in reversed order so we add it to the front. // At this point, the return fiber's effect list is empty except for // deletions, so we can just append the deletion to the list. The remaining // effects aren't added until the complete phase. Once we implement // resuming, this may not be true. const last = returnFiber.lastEffect; // 存在 last if (last !== null) { last.nextEffect = childToDelete; returnFiber.lastEffect = childToDelete; } else { // 不存在 last returnFiber.firstEffect = returnFiber.lastEffect = childToDelete; } childToDelete.nextEffect = null; // 已经是要删除的节点,上面的其他副作用都是没有任何意义的 // effectTag 就是告诉后续的 commit 阶段,对于这个节点需要执行什么操作,可以看到,这里是删除 childToDelete.effectTag = Deletion; // 只需要设置这个即可 }
- 进入
-
reconcileChildrenArray
和reconcileChildrenIterator
这两个api比较复杂,先跳过文章来源:https://www.toymoban.com/news/detail-808019.html -
其他的 API 都是比较简单了文章来源地址https://www.toymoban.com/news/detail-808019.html
到了这里,关于React16源码: React中的reconcileChildren的源码实现的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!