beginWork - fiber的更新
在上一章中,beginWork
阶段对每一个不同的标签都创建或复用了对应的fiber
,接下来就是对fiber
进行一些更加精细的处理。
updateClassComponent
prepareToReadContext(workInProgress, renderLanes);
第一步是判断context
是否有更新,如果有的话那么标记didReceiveUpdate
为true
。接着获取instance
:
const instance = workInProgress.stateNode;
if (instance === null) {
constructClassInstance(workInProgress, Component, nextProps);
mountClassInstance(workInProgress, Component, nextProps, renderLanes);
}
instance
为class
组件的实例,存储在fiber.stateNode
上。如果instance
不存在,说明还没有被实例化。
constructClassInstance方法
首先第一步是判断是否有contextType
属性且为对象形式。如果存在说明使用了context
,此时会进行readContext
,将读取得到的context
形成链表形式,存放到fiber.dependencies.firstContext
上:
const contextType = ctor.contextType;
if (typeof contextType === 'object' && contextType !== null) {
context = readContext((contextType: any));
}
随后对class
进行实例化,并获取class
组件的state
,存放到fiber.memoizedState
属性上:
let instance = new ctor(props, context);
const state = (workInProgress.memoizedState =
instance.state !== null && instance.state !== undefined
? instance.state
: null);
最后执行adoptClassInstance
方法,建立fiber
和instance
的联系。并且为instance
添加updater
,即setState、forceUpdate
等方法。
instance.updater = classComponentUpdater;
// class fiber 的 stateNode 指向的是 class 实例
workInProgress.stateNode = instance;
// 将 fiber 存储到实例的_reactInternals 属性上
setInstance(instance, workInProgress);
mountClassInstance方法
首先会读取context
值,存放到instance.context
上:
const contextType = ctor.contextType;
if (typeof contextType === 'object' && contextType !== null) {
// 因此在 class 组件里可以通过 this.context 访问 context
instance.context = readContext(contextType);
}
随后是生命周期的执行,首先是静态方法getDerivedStateFromProps
的执行:
let partialState = getDerivedStateFromProps(nextProps, prevState);
const memoizedState =
partialState === null || partialState === undefined
? prevState
: Object.assign({}, prevState, partialState);
workInProgress.memoizedState = memoizedState;
该方法返回的state
会和class
组件原有的state
合并,并且会覆盖原有的state
。
接下来的判断就是为了兼容componentWillMount
,但是已经不推荐使用了。
if (
typeof ctor.getDerivedStateFromProps !== 'function' &&
typeof instance.getSnapshotBeforeUpdate !== 'function' &&
(typeof instance.UNSAFE_componentWillMount === 'function' ||
typeof instance.componentWillMount === 'function')
) {}
最后如果有componentDidMount
,那么会将fiber
标记为Update
,这个标记只是为了后续触发该生命周期,并不是有什么更新操作:
if (typeof instance.componentDidMount === 'function') {
let fiberFlags: Flags = Update;
workInProgress.flags |= fiberFlags;
}
resumeMountClassInstance方法
当instance
存在current
不存在时,可以复用instance
。与mountClassInstance
不同的是它会首先判断是否有componentWillReceiveProps
方法(已不推荐使用),如果存在则会调用。
其次是执行update
队列,更新state
:
const oldState = workInProgress.memoizedState;
let newState = (instance.state = oldState);
processUpdateQueue(workInProgress, newProps, instance, renderLanes);
newState = workInProgress.memoizedState;
如果前后没有更新过,那么只会在componentDidMount
存在时标记为Update
。
if (
oldProps === newProps &&
oldState === newState &&
!hasContextChanged() &&
!checkHasForceUpdateAfterProcessing()
) {
if (typeof instance.componentDidMount === 'function') {
let fiberFlags: Flags = Update;
if (enableSuspenseLayoutEffectSemantics) {
fiberFlags |= LayoutStatic;
}
workInProgress.flags |= fiberFlags;
}
return false
}
否则,会调用getDerivedStateFromProps
方法。调用完成后再次判断是否有更新,并在componentDidMount
存在时标记为Update
。
const shouldUpdate =
// hasForUpdate
checkHasForceUpdateAfterProcessing() ||
// 对 shouldComponentUpdate 和 isPureReactComponent 进行判断
checkShouldComponentUpdate(
workInProgress,
ctor,
oldProps,
newProps,
oldState,
newState,
nextContext,
);
updateClassInstance方法
updateClassInstance
与resumeMountClassInstance
大同小异,区别在于使用componentDidUpdate
时将fiber
标记为Update
。
finishClassComponent方法
finishClassComponent
方法最主要的工作是执行render
方法,然后reconcileChildren
。
const instance = workInProgress.stateNode;
nextChildren = instance.render();
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
updateContextProvider
在React
包中定义了createContext
方法,该方法返回一个context
:
export function createContext<T>(defaultValue: T): ReactContext<T> {
const context: ReactContext<T> = {
$$typeof: REACT_CONTEXT_TYPE,
_currentValue: defaultValue,
_currentValue2: defaultValue,
_threadCount: 0,
Provider: (null: any),
Consumer: (null: any),
};
context.Provider = {
$$typeof: REACT_PROVIDER_TYPE,
_context: context,
};
return context;
}
因此jsx
在解析Provider
的时候,得到的type
为:
{
$$typeof: REACT_PROVIDER_TYPE,
_context: context,
}
更新时,会获取Provider
上定义的value
属性,并进行对比:
if (is(oldValue, newValue)) {
if (
oldProps.children === newProps.children &&
!hasLegacyContextChanged()
) {
return bailoutOnAlreadyFinishedWork(
current,
workInProgress,
renderLanes,
);
}
} else {
// context 改变了,寻找 consumer 、 classComponent 并标记 更新
propagateContextChange(workInProgress, context, renderLanes);
}
如果前后的value
值未发生变化,那么跳过该节点的协调,否则调用propagateContextChange
方法,该方法会以当前节点为根节点向下进行深度遍历。如果节点fiber
上的dependencies
存在,说明使用了context
。遍历dependencies
看是否有使用Provider
变动对应的context
。
if (dependency.context === context) {}
如果当前的fiber.tag === ClassComponent
,由于context
变化,所以会创建一个update
,添加到fiber.updateQueue.shared.pending
当中去。除此之外,还会进行lane
的合并,这样子节点就能进行相应的更新了。
fiber.lanes = mergeLanes(fiber.lanes, renderLanes);
list.lanes = mergeLanes(list.lanes, renderLanes);
如果查找到节点为Provider
,且它们的type
相同(意味着是同一Provider
),那么就会跳过,不做任何处理。
else if (fiber.tag === ContextProvider) {
// 遇到下一个 Provider 停止向下递归
nextFiber = fiber.type === workInProgress.type ? null : fiber.child;
}
updateContextConsumer
如果遇到的是Consumer
标签,首先会执行prepareToReadContext
方法,但是此时是不存在dependencies
,因此没有:
const firstContext = dependencies.firstContext;
if (firstContext !== null) {
if (includesSomeLane(dependencies.lanes, renderLanes)) {
markWorkInProgressReceivedUpdate();
}
dependencies.firstContext = null;
}
判断dependencies.firstContext
是否存在且lane
与renderLanes
有交集,有的话说明还有待更新的内容,此时将didReceiveUpdate
设为true
,firstContext
置为空。
紧接着readContext
,这里的type
就是createContext
创建的context
:
let context: ReactContext<any> = workInProgress.type;
const newValue = readContext(context);
readContext
的过程就是将context
添加到dependencies
当中并形成链表结构:
const contextItem = {
context: ((context: any): ReactContext<mixed>),
memoizedValue: value,
next: null,
}
lastContextDependency = contextItem;
currentlyRenderingFiber.dependencies = {
lanes: NoLanes,
firstContext: contextItem,
};
随后将从context
中读取到的value
进行渲染,达到value
传递的目的:
newChildren = render(newValue);
最终得到可以使用value
的children
。
updateFunctionComponent
对于function
组件,第一步也是处理context
:
prepareToReadContext(workInProgress, renderLanes);
第二步是调用renderWithHooks
方法,其主要目的是执行函数,在执行的过程中处理hooks
。
// 赋值 hooks dispatcher。
ReactCurrentDispatcher.current =
current === null || current.memoizedState === null
? HooksDispatcherOnMount
: HooksDispatcherOnUpdate;
let children = Component(props, secondArg);
这里的第一个参数props
好理解,第二个参数secondArg
在forwardRef
的时候为ref
。另外关于hooks
相关的内容后续章节会单独讲解。
updateForwardRef
forwardRef
方法在React
包中定义:
export function forwardRef<Props, ElementType: React$ElementType>(
render: (props: Props, ref: React$Ref<ElementType>) => React$Node,
) {
// 相当于通过 render 创建了一个节点
const elementType = {
$$typeof: REACT_FORWARD_REF_TYPE,
render,
};
return elementType;
}
在beginWork
的时候先获取到render
,然后其他操作同函数式组件,会进行renderWithHooks
。
const render = Component.render;
const ref = workInProgress.ref;
nextChildren = renderWithHooks(
current,
workInProgress,
render,
nextProps,
ref,
renderLanes,
)
唯一不同的是这个位置传入了ref
值,在调用render
的时候,第二个参数存在并且为传入的ref
。
let children = Component(props, secondArg);
updateHostComponent
updateHostComponent
方法比较简单,主要代码为:
const isDirectTextChild = shouldSetTextContent(type, nextProps);
// ...
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
通过shouldSetTextContent
判断节点是否可以按照文本节点处理,相当于做了一个小优化。
updateFragment
updateFragment
也比较简单,相当于只包装了一层空壳,不对这层壳做处理,直接处理其子元素:
const nextChildren = workInProgress.pendingProps;
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
updateMemoComponent
memo
方法在React
包中定义:
export function memo<Props>(
type: React$ElementType,
compare?: (oldProps: Props, newProps: Props) => boolean,
) {
const elementType = {
$$typeof: REACT_MEMO_TYPE,
type,
compare: compare === undefined ? null : compare,
};
return elementType;
}
如果current
不存在,说明不可复用,此时需要为被memo
的组件创建fiber
:
const child = createFiberFromTypeAndProps(
Component.type,
null,
nextProps,
workInProgress,
workInProgress.mode,
renderLanes,
);
如果current
存在,那么current.child
就是被memo
包裹的组件的fiber
。接着判断组件有没有被更新:
const hasScheduledUpdateOrContext = checkScheduledUpdateOrContext(
current,
renderLanes,
);
如果被更新了,那么会通过createWorkInProgress
创建(或复用)之前的fiber
。否则通过传入的compare
函数比较是否需要更新:
if (!hasScheduledUpdateOrContext) {
const prevProps = currentChild.memoizedProps;
let compare = Component.compare;
compare = compare !== null ? compare : shallowEqual;
if (compare(prevProps, nextProps) && current.ref === workInProgress.ref) {
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}
}
updatePortalComponent
createPortal
方法在React-Dom
包中定义:
export function createPortal(
children: ReactNodeList,
containerInfo: any,
implementation: any,
key: ?string = null,
): ReactPortal {
return {
$$typeof: REACT_PORTAL_TYPE,
key: key == null ? null : '' + key,
children,
containerInfo,
implementation,
};
}
在创建fiber
时,它的stateNode
属性是一个有containerInfo
属性的对象:
export function createFiberFromPortal(
portal: ReactPortal,
mode: TypeOfMode,
lanes: Lanes,
): Fiber {
const pendingProps = portal.children !== null ? portal.children : [];
const fiber = createFiber(HostPortal, pendingProps, portal.key, mode);
fiber.lanes = lanes;
fiber.stateNode = {
containerInfo: portal.containerInfo,
pendingChildren: null, // Used by persistent updates
implementation: portal.implementation,
};
return fiber;
}
在beginWork
更新时,会将containerInfo
存放起来:
pushHostContainer(workInProgress, workInProgress.stateNode.containerInfo);
在它的子节点被添加时,可以找到这个containerInfo
节点。这样就可以达到fiber
在rootFiber
内,但是添加的真实节点在其他节点的目的。
mountLazyComponent
lazy
的定义在React
包中,主要由两个函数构成:
function lazyInitializer<T>(payload: Payload<T>): T {
if (payload._status === Uninitialized) {
const ctor = payload._result;
const thenable = ctor();
thenable.then(
moduleObject => {
if (payload._status === Pending || payload._status === Uninitialized) {
const resolved: ResolvedPayload<T> = (payload: any);
resolved._status = Resolved;
resolved._result = moduleObject;
}
},
error => {
if (payload._status === Pending || payload._status === Uninitialized) {
const rejected: RejectedPayload = (payload: any);
rejected._status = Rejected;
rejected._result = error;
}
},
);
if (payload._status === Uninitialized) {
const pending: PendingPayload = (payload: any);
pending._status = Pending;
pending._result = thenable;
}
}
if (payload._status === Resolved) {
// 如果 promise 完成了,那么会将加载的模块返回
const moduleObject = payload._result;
return moduleObject.default;
} else {
// 第一次加载时,报错
throw payload._result;
}
}
export function lazy<T>(
ctor: () => Thenable<{default: T, ...}>,
): LazyComponent<T, Payload<T>> {
const payload: Payload<T> = {
_status: -1,
_result: ctor,
};
// 返回的 type
const lazyType: LazyComponent<T, Payload<T>> = {
$$typeof: REACT_LAZY_TYPE,
_payload: payload,
_init: lazyInitializer,
};
return lazyType;
}
在beginWork
的时候,会正式执行该promise
:
const lazyComponent: LazyComponentType<any, any> = elementType;
const payload = lazyComponent._payload;
const init = lazyComponent._init;
let Component = init(payload);
第一次执行时,由于promise
没有resolve
,因此会报错,此时会被外围的catch
捕捉,该过程后续会在Suspense
章节中详细讲解。
如果能正常加载该组件,那么会根据组件的类型去调用对应的函数:
workInProgress.type = Component;
// 获取 tag
const resolvedTag = (workInProgress.tag = resolveLazyComponentTag(Component));
const resolvedProps = resolveDefaultProps(Component, props);
let child;
switch (resolvedTag) {
case FunctionComponent: { // ... }
case ClassComponent: { // ... }
case ForwardRef: { // ... }
case MemoComponent: { // ... }
}