1 )概述
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,
);
}
}
mountChildFibers
, reconcileChildFibers
reconcileChildFibers
export const reconcileChildFibers = ChildReconciler(true);
export const mountChildFibers = ChildReconciler(false);
整体框架
// 参数 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);
};
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
这个api
function 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比较复杂,先跳过
其他的 API 都是比较简单了