Skip to content

beginWork - fiber的更新

在上一章中,beginWork阶段对每一个不同的标签都创建或复用了对应的fiber,接下来就是对fiber进行一些更加精细的处理。

updateClassComponent

javascript
prepareToReadContext(workInProgress, renderLanes);

第一步是判断context是否有更新,如果有的话那么标记didReceiveUpdatetrue。接着获取instance:

javascript
const instance = workInProgress.stateNode;
if (instance === null) {
  constructClassInstance(workInProgress, Component, nextProps);
  mountClassInstance(workInProgress, Component, nextProps, renderLanes);
}

instanceclass组件的实例,存储在fiber.stateNode上。如果instance不存在,说明还没有被实例化。

constructClassInstance方法

首先第一步是判断是否有contextType属性且为对象形式。如果存在说明使用了context,此时会进行readContext,将读取得到的context形成链表形式,存放到fiber.dependencies.firstContext上:

javascript
const contextType = ctor.contextType;
if (typeof contextType === 'object' && contextType !== null) {
  context = readContext((contextType: any));
}

随后对class进行实例化,并获取class组件的state,存放到fiber.memoizedState属性上:

javascript
let instance = new ctor(props, context);

const state = (workInProgress.memoizedState =
  instance.state !== null && instance.state !== undefined
    ? instance.state
    : null);

最后执行adoptClassInstance方法,建立fiberinstance的联系。并且为instance添加updater,即setState、forceUpdate等方法。

javascript
instance.updater = classComponentUpdater;
// class fiber 的 stateNode 指向的是 class 实例
workInProgress.stateNode = instance;
// 将 fiber 存储到实例的_reactInternals 属性上
setInstance(instance, workInProgress);

mountClassInstance方法

首先会读取context值,存放到instance.context上:

javascript
const contextType = ctor.contextType;
if (typeof contextType === 'object' && contextType !== null) {
  // 因此在 class 组件里可以通过 this.context 访问 context
  instance.context = readContext(contextType);
}

随后是生命周期的执行,首先是静态方法getDerivedStateFromProps的执行:

javascript
let partialState = getDerivedStateFromProps(nextProps, prevState);
const memoizedState =
  partialState === null || partialState === undefined
    ? prevState
  : Object.assign({}, prevState, partialState);
workInProgress.memoizedState = memoizedState;

该方法返回的state会和class组件原有的state合并,并且会覆盖原有的state

接下来的判断就是为了兼容componentWillMount,但是已经不推荐使用了。

javascript
if (
  typeof ctor.getDerivedStateFromProps !== 'function' &&
  typeof instance.getSnapshotBeforeUpdate !== 'function' &&
  (typeof instance.UNSAFE_componentWillMount === 'function' ||
    typeof instance.componentWillMount === 'function')
) {}

最后如果有componentDidMount,那么会将fiber标记为Update,这个标记只是为了后续触发该生命周期,并不是有什么更新操作:

javascript
if (typeof instance.componentDidMount === 'function') {
  let fiberFlags: Flags = Update;
  workInProgress.flags |= fiberFlags;
}

resumeMountClassInstance方法

instance存在current不存在时,可以复用instance。与mountClassInstance不同的是它会首先判断是否有componentWillReceiveProps方法(已不推荐使用),如果存在则会调用。

其次是执行update队列,更新state:

javascript
const oldState = workInProgress.memoizedState;
let newState = (instance.state = oldState);
processUpdateQueue(workInProgress, newProps, instance, renderLanes);
newState = workInProgress.memoizedState;

如果前后没有更新过,那么只会在componentDidMount存在时标记为Update

javascript
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

javascript
const shouldUpdate =
  // hasForUpdate
  checkHasForceUpdateAfterProcessing() ||
  // 对 shouldComponentUpdate 和 isPureReactComponent 进行判断
  checkShouldComponentUpdate(
    workInProgress,
    ctor,
    oldProps,
    newProps,
    oldState,
    newState,
    nextContext,
  );

updateClassInstance方法

updateClassInstanceresumeMountClassInstance大同小异,区别在于使用componentDidUpdate时将fiber标记为Update

finishClassComponent方法

finishClassComponent方法最主要的工作是执行render方法,然后reconcileChildren

javascript
const instance = workInProgress.stateNode;
nextChildren = instance.render();
reconcileChildren(current, workInProgress, nextChildren, renderLanes);

updateContextProvider

React包中定义了createContext方法,该方法返回一个context

javascript
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为:

javascript
{
  $$typeof: REACT_PROVIDER_TYPE,
   _context: context,
}

更新时,会获取Provider上定义的value属性,并进行对比:

javascript
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

javascript
 if (dependency.context === context) {}

如果当前的fiber.tag === ClassComponent,由于context变化,所以会创建一个update,添加到fiber.updateQueue.shared.pending当中去。除此之外,还会进行lane的合并,这样子节点就能进行相应的更新了。

javascript
fiber.lanes = mergeLanes(fiber.lanes, renderLanes);
list.lanes = mergeLanes(list.lanes, renderLanes);

如果查找到节点为Provider,且它们的type相同(意味着是同一Provider),那么就会跳过,不做任何处理。

javascript
else if (fiber.tag === ContextProvider) {
 // 遇到下一个 Provider 停止向下递归
 nextFiber = fiber.type === workInProgress.type ? null : fiber.child;
}

updateContextConsumer

如果遇到的是Consumer标签,首先会执行prepareToReadContext方法,但是此时是不存在dependencies,因此没有:

javascript
const firstContext = dependencies.firstContext;
if (firstContext !== null) {
  if (includesSomeLane(dependencies.lanes, renderLanes)) {
    markWorkInProgressReceivedUpdate();
  }
  dependencies.firstContext = null;
}

判断dependencies.firstContext是否存在且lanerenderLanes有交集,有的话说明还有待更新的内容,此时将didReceiveUpdate设为truefirstContext置为空。

紧接着readContext,这里的type就是createContext创建的context

javascript
let context: ReactContext<any> = workInProgress.type;
const newValue = readContext(context);

readContext的过程就是将context添加到dependencies当中并形成链表结构:

javascript
const contextItem = {
 context: ((context: any): ReactContext<mixed>),
 memoizedValue: value,
 next: null,
}
   
lastContextDependency = contextItem;
currentlyRenderingFiber.dependencies = {
 lanes: NoLanes,
 firstContext: contextItem,
};

随后将从context中读取到的value进行渲染,达到value传递的目的:

javascript
newChildren = render(newValue);

最终得到可以使用valuechildren

updateFunctionComponent

对于function组件,第一步也是处理context

javascript
prepareToReadContext(workInProgress, renderLanes);

第二步是调用renderWithHooks方法,其主要目的是执行函数,在执行的过程中处理hooks

javascript
// 赋值 hooks dispatcher。
ReactCurrentDispatcher.current =
  current === null || current.memoizedState === null
    ? HooksDispatcherOnMount
    : HooksDispatcherOnUpdate;
let children = Component(props, secondArg);

这里的第一个参数props好理解,第二个参数secondArgforwardRef的时候为ref。另外关于hooks相关的内容后续章节会单独讲解。

updateForwardRef

forwardRef方法在React包中定义:

javascript
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

javascript
const render = Component.render;
const ref = workInProgress.ref;
nextChildren = renderWithHooks(
  current,
  workInProgress,
  render,
  nextProps,
  ref,
  renderLanes,
)

唯一不同的是这个位置传入了ref值,在调用render的时候,第二个参数存在并且为传入的ref

javascript
let children = Component(props, secondArg);

updateHostComponent

updateHostComponent方法比较简单,主要代码为:

javascript
const isDirectTextChild = shouldSetTextContent(type, nextProps);
// ...
reconcileChildren(current, workInProgress, nextChildren, renderLanes);

通过shouldSetTextContent判断节点是否可以按照文本节点处理,相当于做了一个小优化。

updateFragment

updateFragment也比较简单,相当于只包装了一层空壳,不对这层壳做处理,直接处理其子元素:

javascript
const nextChildren = workInProgress.pendingProps;
reconcileChildren(current, workInProgress, nextChildren, renderLanes);

updateMemoComponent

memo方法在React包中定义:

javascript
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

javascript
const child = createFiberFromTypeAndProps(
 Component.type,
 null,
 nextProps,
 workInProgress,
 workInProgress.mode,
 renderLanes,
);

如果current存在,那么current.child就是被memo包裹的组件的fiber。接着判断组件有没有被更新:

javascript
const hasScheduledUpdateOrContext = checkScheduledUpdateOrContext(
  current,
  renderLanes,
);

如果被更新了,那么会通过createWorkInProgress创建(或复用)之前的fiber。否则通过传入的compare函数比较是否需要更新:

javascript
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包中定义:

javascript
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属性的对象:

javascript
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存放起来:

javascript
pushHostContainer(workInProgress, workInProgress.stateNode.containerInfo);

在它的子节点被添加时,可以找到这个containerInfo节点。这样就可以达到fiberrootFiber内,但是添加的真实节点在其他节点的目的。

mountLazyComponent

lazy的定义在React包中,主要由两个函数构成:

javascript
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:

javascript
const lazyComponent: LazyComponentType<any, any> = elementType;
const payload = lazyComponent._payload;
const init = lazyComponent._init;
let Component = init(payload);

第一次执行时,由于promise没有resolve,因此会报错,此时会被外围的catch捕捉,该过程后续会在Suspense章节中详细讲解。

如果能正常加载该组件,那么会根据组件的类型去调用对应的函数:

javascript
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: { // ... }
}