Coder Social home page Coder Social logo

react-source-code's People

Contributors

lz-lee avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

react-source-code's Issues

ClassComponent 更新过程

classComponent 更新 updateClassComponent

  • 承担了唯二可以更新应用的api: setStateforceUpdate

  • 首次渲染 instancenull

    • constructClassInstance 创建实例
    • mountClassInstance 挂载实例,调用 willMount
      • 如果有 getDerivedStateFromProps 则会执行并更新 state,并且不会执行 willMount 钩子
      • 如果有 didMount 则标记 workInProgresseffectTagUpdate
  • 渲染被中断 instance !== null, current === null

    • resumeMountClassInstance 复用实例但还是调用首次渲染的生命周期方法
      const hasNewLifecycles =
      typeof getDerivedStateFromProps === 'function' ||
      typeof instance.getSnapshotBeforeUpdate === 'function';
      
      • 如果有 getDerivedStateFromProps 则会执行并更新 state
      • 如果 hasNewLifecyclestrue, 则不会调用 componentWillReceivePropswillMount 钩子,否则先调用 componentWillReceiveProps 再调用 componentWillMount
      • 如果有 didMount 则标记 workInProgresseffectTagUpdate
  • 更新渲染 instance !== null, current !== null

    • updateClassInstance,调用 componentWillReceivePropwillUpdate 生命周期
      • 如果有 getDerivedStateFromProps 则会执行并更新 state
      • 如果 hasNewLifecyclestruecomponentWillReceivePropwillUpdate 不会调用
      • 如果 shouldUpdatetrue,并有 didUpdate 方法则标记 workInProgresseffectTagUpdate
  • 初次渲染 updateUqueuenull,当有 setStateupdateQueue 可能有多个 update ,需要调用 processUpdateQueue 更新 state

  • 最后执行 finishClassComponent

    • 进行错误判断的处理
    • 判断是否可以跳过更新
    • 重新调和子节点 reconcileChildren
case ClassComponent: {
      const Component = workInProgress.type;
      const unresolvedProps = workInProgress.pendingProps;
      const resolvedProps =
        workInProgress.elementType === Component
          ? unresolvedProps
          : resolveDefaultProps(Component, unresolvedProps);
      return updateClassComponent(
        current,
        workInProgress,
        Component,
        resolvedProps,
        renderExpirationTime,
      );
    }

updateClassComponent

function updateClassComponent(
  current: Fiber | null,
  workInProgress: Fiber,
  Component: any,
  nextProps,
  renderExpirationTime: ExpirationTime,
) {
  // ...context 相关

  // classComponent 通过 new class 获取的实例对象
  const instance = workInProgress.stateNode;

  // 通过这个变量判断是否更新component
  let shouldUpdate;
  // 首次 render 时不存在,或者class组件第一次渲染
  if (instance === null) {
     // 如果非首次渲染,而 instance 不存在,表示的是 suspended 异步组件的情况
    if (current !== null) {
      // An class component without an instance only mounts if it suspended
      // inside a non- concurrent tree, in an inconsistent state. We want to
      // tree it like a new mount, even though an empty version of it already
      // committed. Disconnect the alternate pointers.
      current.alternate = null;
      workInProgress.alternate = null;
      // Since this is conceptually a new fiber, schedule a Placement effect
      workInProgress.effectTag |= Placement;
    }
    // In the initial pass we might need to construct the instance.
    // 创建 instance
    constructClassInstance(
      workInProgress,
      Component,
      nextProps,
      renderExpirationTime,
    );
    // 挂载 class组件
    mountClassInstance(
      workInProgress,
      Component,
      nextProps,
      renderExpirationTime,
    );
    shouldUpdate = true;
  } else if (current === null) {
    // instance !==  null,之前首次挂载时被中断的,创建了 instance 但在 render 中报错的情况
    // In a resume, we'll already have an instance we can reuse.
    // 复用 instance
    shouldUpdate = resumeMountClassInstance(
      workInProgress,
      Component,
      nextProps,
      renderExpirationTime,
    );
  } else {
    // 更新渲染
    shouldUpdate = updateClassInstance(
      current,
      workInProgress,
      Component,
      nextProps,
      renderExpirationTime,
    );
  }
  return finishClassComponent(
    current,
    workInProgress,
    Component,
    shouldUpdate,
    hasContext,
    renderExpirationTime,
  );
}

constructClassInstance 创建实例

function constructClassInstance(
  workInProgress: Fiber,
  ctor: any,
  props: any,
  renderExpirationTime: ExpirationTime,
): any {
  let isLegacyContextConsumer = false;
  let unmaskedContext = emptyContextObject;
  let context = null;
  const contextType = ctor.contextType;
  // context 相关

  // ctor 就是 Reactelemnt.type 即声明组件的组件名,这里生成组件实例
  const instance = new ctor(props, context);
  // 初始化 state
  const state = (workInProgress.memoizedState =
    instance.state !== null && instance.state !== undefined
      ? instance.state
      : null);
      // 为 instance 添加 updater 为 classComponentUpdater,
      // 指定 workInProgress 的 stateNode 为当前instance
      // ReactInstanceMap map 对象, 给 instance._reactInternaleFiber 赋值当前 workInprogress
  adoptClassInstance(workInProgress, instance);

  // 创建实例时的警告,使用 getDerivedStateFromProps 不推荐设置 state 为 null
  // 使用了 getDerivedStateFromProps 或者getSnapshotBeforeUpdate, willXXXX 声明周期方法不会执行
  if (__DEV__) {
    // ...
  }

  // Cache unmasked context so we can avoid recreating masked context unless necessary.
  // ReactFiberContext usually updates this cache but can't for newly-created instances.
  // context 相关
  if (isLegacyContextConsumer) {
    cacheContext(workInProgress, unmaskedContext, context);
  }

  return instance;
}

// adoptClassInstance 方法

function adoptClassInstance(workInProgress: Fiber, instance: any): void {
  instance.updater = classComponentUpdater;
  workInProgress.stateNode = instance;
  // The instance needs access to the fiber so that it can schedule updates
  ReactInstanceMap.set(instance, workInProgress);
  if (__DEV__) {
    instance._reactInternalInstance = fakeInternalInstance;
  }
}

mountClassInstance 挂载实例

function mountClassInstance(
  workInProgress: Fiber,
  ctor: any,
  newProps: any,
  renderExpirationTime: ExpirationTime,
): void {
  if (__DEV__) {
    checkClassInstance(workInProgress, ctor, newProps);
  }

  const instance = workInProgress.stateNode;
  instance.props = newProps;
  instance.state = workInProgress.memoizedState;
  instance.refs = emptyRefsObject;

  const contextType = ctor.contextType;
  if (typeof contextType === 'object' && contextType !== null) {
    instance.context = readContext(contextType);
  } else {
    const unmaskedContext = getUnmaskedContext(workInProgress, ctor, true);
    instance.context = getMaskedContext(workInProgress, unmaskedContext);
  }

  if (__DEV__) {
   // .... 
  }

  // 初次渲染 updateUqueue 为 null,当有 setState 时 updateQueue 可能有多个 update ,需要调用 processUpdateQueue 更新 state
  let updateQueue = workInProgress.updateQueue;
  if (updateQueue !== null) {
    processUpdateQueue(
      workInProgress,
      updateQueue,
      newProps,
      instance,
      renderExpirationTime,
    );
    // 更新 instance 上的 state
    instance.state = workInProgress.memoizedState;
  }
  // 新的声明周期,组件更新过程中调用,即拿到的是更新后的 state
  const getDerivedStateFromProps = ctor.getDerivedStateFromProps;
  if (typeof getDerivedStateFromProps === 'function') {
    applyDerivedStateFromProps(
      workInProgress,
      ctor,
      getDerivedStateFromProps,
      newProps,
    );
    // 更新 state
    instance.state = workInProgress.memoizedState;
  }

  // In order to support react-lifecycles-compat polyfilled components,
  // Unsafe lifecycles should not be invoked for components using the new APIs.
  // 用了新的 API 则 willMount 不会执行
  if (
    typeof ctor.getDerivedStateFromProps !== 'function' &&
    typeof instance.getSnapshotBeforeUpdate !== 'function' &&
    (typeof instance.UNSAFE_componentWillMount === 'function' ||
      typeof instance.componentWillMount === 'function')
  ) {
    // willMount 可能执行了 setState 因此需要执行 processUpdateQueue 来更新 state
    callComponentWillMount(workInProgress, instance);
    // If we had additional state updates during this life-cycle, let's
    // process them now.
    updateQueue = workInProgress.updateQueue;
    if (updateQueue !== null) {
      processUpdateQueue(
        workInProgress,
        updateQueue,
        newProps,
        instance,
        renderExpirationTime,
      );
      instance.state = workInProgress.memoizedState;
    }
  }

  // 如果有 didMount,标记 effect 为 update,在 commit 阶段时,表明组件是有 didmount 这个方法的,
  // 等到组件内容真正 mount 到 dom 上时,才能真正调用 didmount 这个方法,等到需要用的时候才会去调用
  if (typeof instance.componentDidMount === 'function') {
    workInProgress.effectTag |= Update;
  }
}

processUpdateQueue 更新state

  • 都是基于 queue.baseState 来计算新的 state 的,遍历 updateQueue 过程中会将每个 update 的更新优先级与当前更新优先级做对比,如果低于当前更新优先级则不执行

  • 没有剩下来的 update 和 captureUpdate 表示全部计算完了,更新 baseState 为计算后的 resultState

export function processUpdateQueue<State>(
  workInProgress: Fiber,
  queue: UpdateQueue<State>,
  props: any,
  instance: any,
  renderExpirationTime: ExpirationTime,
): void {
  hasForceUpdate = false;

  // 保证 workInProgress 上的 updateQueue 是 clone 的 queue,防止直接修改 current上的 queue
  queue = ensureWorkInProgressQueueIsAClone(workInProgress, queue);

  if (__DEV__) {
    currentlyProcessingQueue = queue;
  }

  // These values may change as we process the queue.
  // 每次执行完 processUpdateQueue 都会把新的 state 赋值到 baseState 上
  let newBaseState = queue.baseState;
  let newFirstUpdate = null;
  let newExpirationTime = NoWork;

  // Iterate through the list of updates to compute the result.
  let update = queue.firstUpdate;
  let resultState = newBaseState;
  while (update !== null) {
    // update的执行优先级
    const updateExpirationTime = update.expirationTime;
    // 优先级低则不执行
    if (updateExpirationTime > renderExpirationTime) {
      // This update does not have sufficient priority. Skip it.
      if (newFirstUpdate === null) {
        // This is the first skipped update. It will be the first update in
        // the new list.
        // 此 update 就成为剩下的第一个待更新的
        newFirstUpdate = update;
        // Since this is the first update that was skipped, the current result
        // is the new base state.
        newBaseState = resultState;
      }
      // Since this update will remain in the list, update the remaining
      // expiration time.
      if (
        newExpirationTime === NoWork ||
        newExpirationTime > updateExpirationTime
      ) {
        // 不更新则赋值为此 update 的过期时间
        newExpirationTime = updateExpirationTime;
      }
    } else {
      // This update does have sufficient priority. Process it and compute
      // a new result.
      // 这次 update 要被执行, setState 的 update.tag -> UpdateState -> 计算新的 state
      // 如果为 forceUpdate 则更新全局变量 hasForceUpdate = true
      resultState = getStateFromUpdate(
        workInProgress,
        queue,
        update,
        resultState, // prevState 为之前计算的 baseState
        props,
        instance,
      );
      // setState(fn ,callback) callback 为第二个参数,在 setState 之后执行
      const callback = update.callback;
      if (callback !== null) {
        // 标记 effect 在 commit 阶段,setState 更新完成之后执行
        workInProgress.effectTag |= Callback;
        // Set this to null, in case it was mutated during an aborted render.
        // 防止中断的 render 过程被修改
        update.nextEffect = null;
        // 保存链表结构
        if (queue.lastEffect === null) {
          queue.firstEffect = queue.lastEffect = update;
        } else {
          queue.lastEffect.nextEffect = update;
          queue.lastEffect = update;
        }
      }
    }
    // 操作下一个 update
    // Continue to the next update.
    update = update.next;
  }

  // 同样的对 captureUpdate 执行更新操作
  // Separately, iterate though the list of captured updates.
  let newFirstCapturedUpdate = null;
  update = queue.firstCapturedUpdate;
  while (update !== null) {
    const updateExpirationTime = update.expirationTime;
    if (updateExpirationTime > renderExpirationTime) {
      // This update does not have sufficient priority. Skip it.
      if (newFirstCapturedUpdate === null) {
        // This is the first skipped captured update. It will be the first
        // update in the new list.
        newFirstCapturedUpdate = update;
        // If this is the first update that was skipped, the current result is
        // the new base state.
        if (newFirstUpdate === null) {
          newBaseState = resultState;
        }
      }
      // Since this update will remain in the list, update the remaining
      // expiration time.
      if (
        newExpirationTime === NoWork ||
        newExpirationTime > updateExpirationTime
      ) {
        newExpirationTime = updateExpirationTime;
      }
    } else {
      // This update does have sufficient priority. Process it and compute
      // a new result.
      resultState = getStateFromUpdate(
        workInProgress,
        queue,
        update,
        resultState,
        props,
        instance,
      );
      const callback = update.callback;
      if (callback !== null) {
        workInProgress.effectTag |= Callback;
        // Set this to null, in case it was mutated during an aborted render.
        update.nextEffect = null;
        if (queue.lastCapturedEffect === null) {
          queue.firstCapturedEffect = queue.lastCapturedEffect = update;
        } else {
          queue.lastCapturedEffect.nextEffect = update;
          queue.lastCapturedEffect = update;
        }
      }
    }
    update = update.next;
  }

  if (newFirstUpdate === null) {
    queue.lastUpdate = null;
  }
  if (newFirstCapturedUpdate === null) {
    queue.lastCapturedUpdate = null;
  } else {
    workInProgress.effectTag |= Callback;
  }
  // 没有剩下来的 update,表示全部计算完了
  if (newFirstUpdate === null && newFirstCapturedUpdate === null) {
    // We processed every update, without skipping. That means the new base
    // state is the same as the result state.
    // 重新赋值 newBaseState 为最后 计算的state
    newBaseState = resultState;
  }

  // 赋值 baseState 为计算后的 state
  queue.baseState = newBaseState;
  queue.firstUpdate = newFirstUpdate;
  queue.firstCapturedUpdate = newFirstCapturedUpdate;

  // Set the remaining expiration time to be whatever is remaining in the queue.
  // This should be fine because the only two other things that contribute to
  // expiration time are props and context. We're already in the middle of the
  // begin phase by the time we start processing the queue, so we've already
  // dealt with the props. Context in components that specify
  // shouldComponentUpdate is tricky; but we'll have to account for
  // that regardless.
  // 需要执行的更新已经更新完了,剩下的更新应该是剩下的 updateQueue 里面的 update 中最高优先级的那个 expirationTime
  workInProgress.expirationTime = newExpirationTime;
  // 更新 state
  workInProgress.memoizedState = resultState;

  if (__DEV__) {
    currentlyProcessingQueue = null;
  }
}

getStateFromUpdate 根据 setState 计算返回新的state

function getStateFromUpdate<State>(
  workInProgress: Fiber,
  queue: UpdateQueue<State>,
  update: Update<State>,
  prevState: State,
  nextProps: any,
  instance: any,
): any {
  switch (update.tag) {
    case ReplaceState: {
      // ...
    }
    case CaptureUpdate: {
      // ...
    }
    // Intentional fallthrough
    case UpdateState: {
      const payload = update.payload;
      let partialState;
      if (typeof payload === 'function') {
        // Updater function
        if (__DEV__) {
          // ...
        }
        // 函数更新 state
        partialState = payload.call(instance, prevState, nextProps);
      } else {
        // 普通对象
        // Partial state object
        partialState = payload;
      }
      if (partialState === null || partialState === undefined) {
        // Null and undefined are treated as no-ops.
        return prevState;
      }
      // Merge the partial state and the previous state.
      // 最后合并原 state 和计算出的新 state
      return Object.assign({}, prevState, partialState);
    }
    // 
    case ForceUpdate: {
      hasForceUpdate = true;
      return prevState;
    }
  }
  return prevState;
}

applyDerivedStateFromProps 执行新声明周期钩子

export function applyDerivedStateFromProps(
  workInProgress: Fiber,
  ctor: any,
  getDerivedStateFromProps: (props: any, state: any) => any,
  nextProps: any,
) {
  // 这里的 state 是更新过后的state
  const prevState = workInProgress.memoizedState;

  // 传入的 state 是经过 processUpdateQueue 计算后的state 
  const partialState = getDerivedStateFromProps(nextProps, prevState);

  // Merge the partial state and the previous state.
  const memoizedState =
    partialState === null || partialState === undefined
      ? prevState
      : Object.assign({}, prevState, partialState);
  // 更新state
  workInProgress.memoizedState = memoizedState;

  // Once the update queue is empty, persist the derived state onto the
  // base state.
  const updateQueue = workInProgress.updateQueue;
  // queue 不为null 且 不渲染的时候,才更新 baseState 
  if (updateQueue !== null && workInProgress.expirationTime === NoWork) {
    updateQueue.baseState = memoizedState;
  }
}

resumeMountClassInstance 复用 instance 的情况

function resumeMountClassInstance(
  workInProgress: Fiber,
  ctor: any,
  newProps: any,
  renderExpirationTime: ExpirationTime,
): boolean {
  const instance = workInProgress.stateNode;

  const oldProps = workInProgress.memoizedProps;
  instance.props = oldProps;

  const getDerivedStateFromProps = ctor.getDerivedStateFromProps;
  const hasNewLifecycles =
    typeof getDerivedStateFromProps === 'function' ||
    typeof instance.getSnapshotBeforeUpdate === 'function';

  // Note: During these life-cycles, instance.props/instance.state are what
  // ever the previously attempted to render - not the "current". However,
  // during componentDidUpdate we pass the "current" props.

  // In order to support react-lifecycles-compat polyfilled components,
  // Unsafe lifecycles should not be invoked for components using the new APIs.
  // 有新 API则不执行 componentWillReceiveProps
  if (
    !hasNewLifecycles &&
    (typeof instance.UNSAFE_componentWillReceiveProps === 'function' ||
      typeof instance.componentWillReceiveProps === 'function')
  ) {
    if (oldProps !== newProps || oldContext !== nextContext) {
      callComponentWillReceiveProps(
        workInProgress,
        instance,
        newProps,
        nextContext,
      );
    }
  }

  // => hasForceUpdate = false
  resetHasForceUpdateBeforeProcessing();

  const oldState = workInProgress.memoizedState;
  let newState = (instance.state = oldState);
  let updateQueue = workInProgress.updateQueue;
  if (updateQueue !== null) {
    processUpdateQueue(
      workInProgress,
      updateQueue,
      newProps,
      instance,
      renderExpirationTime,
    );
    newState = workInProgress.memoizedState;
  }
  if (
    // 新老 props 和 state 都相同
    oldProps === newProps &&
    oldState === newState &&
    // 没有 context 变化
    !hasContextChanged() &&
    // 如果 hasForceUpdate 为 false
    !checkHasForceUpdateAfterProcessing()
  ) {
    // If an update was already in progress, we should schedule an Update
    // effect even though we're bailing out, so that cWU/cDU are called.
    // 如果有 didMount 则标记 effect 为 update
    if (typeof instance.componentDidMount === 'function') {
      workInProgress.effectTag |= Update;
    }
    // return 赋值给了 shouldUpdate, 组件是不需要更新的
    return false;
  }

  // 执行新API
  if (typeof getDerivedStateFromProps === 'function') {
    applyDerivedStateFromProps(
      workInProgress,
      ctor,
      getDerivedStateFromProps,
      newProps,
    );
    newState = workInProgress.memoizedState;
  }

  const shouldUpdate =
    // 如果 hasForceUpdate 为 true
    checkHasForceUpdateAfterProcessing() ||
    // 或者 根据 shouldComponentUpdate 的返回值 或者 pureComponent的情况则调用 shallowEqual 的返回值
    // 默认返回 true
    checkShouldComponentUpdate(
      workInProgress,
      ctor,
      oldProps,
      newProps,
      oldState,
      newState,
      nextContext,
    );

  if (shouldUpdate) {
    // 需要更新组件
    // In order to support react-lifecycles-compat polyfilled components,
    // Unsafe lifecycles should not be invoked for components using the new APIs.
    // 没有使用新声明周期并有 willMount 则调用 willMount
    if (
      !hasNewLifecycles &&
      (typeof instance.UNSAFE_componentWillMount === 'function' ||
        typeof instance.componentWillMount === 'function')
    ) {
      startPhaseTimer(workInProgress, 'componentWillMount');
      if (typeof instance.componentWillMount === 'function') {
        instance.componentWillMount();
      }
      if (typeof instance.UNSAFE_componentWillMount === 'function') {
        instance.UNSAFE_componentWillMount();
      }
      stopPhaseTimer();
    }
    if (typeof instance.componentDidMount === 'function') {
      workInProgress.effectTag |= Update;
    }
    // 都需要标记 didMount ,中断的组件挂载仍然按首次挂载执行
  } else {
    // 不需要更新
    // If an update was already in progress, we should schedule an Update
    // effect even though we're bailing out, so that cWU/cDU are called.
    if (typeof instance.componentDidMount === 'function') {
      workInProgress.effectTag |= Update;
    }

    // If shouldComponentUpdate returned false, we should still update the
    // memoized state to indicate that this work can be reused.
    // 组件不需要更新,仍然更新 memoized 变量
    workInProgress.memoizedProps = newProps;
    workInProgress.memoizedState = newState;
  }

  // Update the existing instance's state, props, and context pointers even
  // if shouldComponentUpdate returns false.
  // instance 上的 state、props、context 始终要更新
  instance.props = newProps;
  instance.state = newState;
  instance.context = nextContext;

  return shouldUpdate;
}

checkShouldComponentUpdate

function checkShouldComponentUpdate(
  workInProgress,
  ctor,
  oldProps,
  newProps,
  oldState,
  newState,
  nextContext,
) {
  const instance = workInProgress.stateNode;
  // shouldComponentUpdate 来判断
  if (typeof instance.shouldComponentUpdate === 'function') {
    startPhaseTimer(workInProgress, 'shouldComponentUpdate');
    const shouldUpdate = instance.shouldComponentUpdate(
      newProps,
      newState,
      nextContext,
    );
    stopPhaseTimer();

    return shouldUpdate;
  }
  // 如果是pureComponent 则浅对比 props 和 state
  if (ctor.prototype && ctor.prototype.isPureReactComponent) {
    return (
      !shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState)
    );
  }

  return true;
}

// shallowEqual

const hasOwnProperty = Object.prototype.hasOwnProperty;

/**
 * inlined Object.is polyfill to avoid requiring consumers ship their own
 * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is
 */
 // polyfill Object.is()
function is(x, y) {
  // SameValue algorithm
  if (x === y) {
    // Steps 1-5, 7-10
    // Steps 6.b-6.e: +0 != -0
    // Added the nonzero y check to make Flow happy, but it is redundant
    return x !== 0 || y !== 0 || 1 / x === 1 / y;
  } else {
    // Step 6.a: NaN == NaN
    return x !== x && y !== y;
  }
}

/**
 * Performs equality by iterating through keys on an object and returning false
 * when any key has values which are not strictly equal between the arguments.
 * Returns true when the values of all keys are strictly equal.
 */
function shallowEqual(objA: mixed, objB: mixed): boolean {
  if (is(objA, objB)) {
    return true;
  }

  if (
    typeof objA !== 'object' ||
    objA === null ||
    typeof objB !== 'object' ||
    objB === null
  ) {
    return false;
  }

  const keysA = Object.keys(objA);
  const keysB = Object.keys(objB);

  if (keysA.length !== keysB.length) {
    return false;
  }

  // Test for A's keys different from B.
  for (let i = 0; i < keysA.length; i++) {
    if (
      !hasOwnProperty.call(objB, keysA[i]) ||
      !is(objA[keysA[i]], objB[keysA[i]])
    ) {
      return false;
    }
  }

  return true;
}

updateClassInstance 更新渲染

  • 过程和 resumeMountClassInstance 相似,不同的是执行的声明周期是 willUpdate,标记 didUpdategetSnapshotBeforeUpdate
if (shouldUpdate) {
    // In order to support react-lifecycles-compat polyfilled components,
    // Unsafe lifecycles should not be invoked for components using the new APIs.
    // 未使用新API ,执行 componentWillUpdate
    if (
      !hasNewLifecycles &&
      (typeof instance.UNSAFE_componentWillUpdate === 'function' ||
        typeof instance.componentWillUpdate === 'function')
    ) {
      startPhaseTimer(workInProgress, 'componentWillUpdate');
      if (typeof instance.componentWillUpdate === 'function') {
        instance.componentWillUpdate(newProps, newState, nextContext);
      }
      if (typeof instance.UNSAFE_componentWillUpdate === 'function') {
        instance.UNSAFE_componentWillUpdate(newProps, newState, nextContext);
      }
      stopPhaseTimer();
    }
    // 标记 didUpdate
    if (typeof instance.componentDidUpdate === 'function') {
      workInProgress.effectTag |= Update;
    }
    // 标记 getSnapshotBeforeUpdate
    if (typeof instance.getSnapshotBeforeUpdate === 'function') {
      workInProgress.effectTag |= Snapshot;
    }
  } else {
    // If an update was already in progress, we should schedule an Update
    // effect even though we're bailing out, so that cWU/cDU are called.
    if (typeof instance.componentDidUpdate === 'function') {
      if (
        oldProps !== current.memoizedProps ||
        oldState !== current.memoizedState
      ) {
        workInProgress.effectTag |= Update;
      }
    }
    if (typeof instance.getSnapshotBeforeUpdate === 'function') {
      if (
        oldProps !== current.memoizedProps ||
        oldState !== current.memoizedState
      ) {
        workInProgress.effectTag |= Snapshot;
      }
    }

    // If shouldComponentUpdate returned false, we should still update the
    // memoized props/state to indicate that this work can be reused.
    workInProgress.memoizedProps = newProps;
    workInProgress.memoizedState = newState;
  }

finishClassComponent 完成 class 组件更新

  • 不需要更新且没错误捕获则跳过更新
  • 组件存在 getDerivedStateFromError 方法,直接执行 instance.render() 获得最新的 nextChildren, 否则 nextChildrennullgetDerivedStateFromError 方法可以在出错后立即生成 state
function finishClassComponent(
  current: Fiber | null,
  workInProgress: Fiber,
  Component: any,
  shouldUpdate: boolean,
  hasContext: boolean,
  renderExpirationTime: ExpirationTime,
) {
  // 标记 ref,仅有在 classComponent 和 HostComponent (原生标签)中标记ref,即只能在这两种类型上 使用 ref
  // Refs should update even if shouldComponentUpdate returns false
  markRef(current, workInProgress);

  const didCaptureError = (workInProgress.effectTag & DidCapture) !== NoEffect;

  // 不需要更新且没有错误则跳过更新
  if (!shouldUpdate && !didCaptureError) {
    // Context providers should defer to sCU for rendering
    if (hasContext) {
      invalidateContextProvider(workInProgress, Component, false);
    }
    // 跳过更新
    return bailoutOnAlreadyFinishedWork(
      current,
      workInProgress,
      renderExpirationTime,
    );
  }

  const instance = workInProgress.stateNode;

  // Rerender
  ReactCurrentOwner.current = workInProgress;
  let nextChildren;
  if (
    didCaptureError &&
    typeof Component.getDerivedStateFromError !== 'function'
  ) {
    // getDerivedStateFromError 方法可以在出错后立即生成 state,防止出错后没有 instance 和 ref 的错误
    // 而 componentDidCatch 是通过 setState 的,要在下次更新才生成新的 state
    // 有错误,且没有 getDerivedStateFromError 方法 则直接渲染空
    // If we captured an error, but getDerivedStateFrom catch is not defined,
    // unmount all the children. componentDidCatch will schedule an update to
    // re-render a fallback. This is temporary until we migrate everyone to
    // the new API.
    // TODO: Warn in a future release.
    nextChildren = null;

    if (enableProfilerTimer) {
      stopProfilerTimerIfRunning(workInProgress);
    }
  } else {
    // 没错误
    // 或者有 getDerivedStateFromError 方法,此方法已经更新了最新的state
    // 通过instance.render 获取最新children
    if (__DEV__) {
      ReactCurrentFiber.setCurrentPhase('render');
      nextChildren = instance.render();
      if (
        debugRenderPhaseSideEffects ||
        (debugRenderPhaseSideEffectsForStrictMode &&
          workInProgress.mode & StrictMode)
      ) {
        instance.render();
      }
      ReactCurrentFiber.setCurrentPhase(null);
    } else {
      nextChildren = instance.render();
    }
  }

  // React DevTools reads this flag.
  workInProgress.effectTag |= PerformedWork;
  // 非首次渲染且有错误
  if (current !== null && didCaptureError) {
    // If we're recovering from an error, reconcile without reusing any of
    // the existing children. Conceptually, the normal children and the children
    // that are shown on error are two different sets, so we shouldn't reuse
    // normal children even if their identities match.
    // 强制的重新计算 child 并 reconcileChildFibers 调和
    forceUnmountCurrentAndReconcile(
      current,
      workInProgress,
      nextChildren,
      renderExpirationTime,
    );
  } else {
    // 没错误或者首次渲染,直接调和
    reconcileChildren(
      current,
      workInProgress,
      nextChildren,
      renderExpirationTime,
    );
  }

  // Memoize state using the values we just used to render.
  // TODO: Restructure so we never read values from the instance.
  workInProgress.memoizedState = instance.state;

  // The context might have changed so we need to recalculate it.
  if (hasContext) {
    invalidateContextProvider(workInProgress, Component, true);
  }
  // 返回 child -> performUnitOfWork -> workLoop 循环
  return workInProgress.child;
}

reactScheduler

reactScheduler 异步任务调度

reactScheduler 核心功能

  • 维护时间片
  • 模拟浏览器 requestldleCallbackAPI (会在浏览器空闲时期依次执行回调函数)
  • 调度列表和任务超时判断

时间片概念

  • 用户感知界面流畅至少需要 1秒30帧的刷新频率(30hz), 1000 / 30, 每帧只有33ms 来执行
  • 一帧 33ms,如果react执行更新需要 20ms,那浏览器执行动画或用户反馈的时间只有 13ms,但是这一帧仍然是可以执行浏览器的动作。
  • 如果 react 更新需要 43ms, 还需要向下一帧借用 10ms, 浏览器在这第一帧中就没有时间去执行自己的任务,就会造成卡顿。
  • requestldleCallback API 会在浏览器空闲时依次调用函数, 让浏览器在每一帧都有足够的时间去执行动画或者用户反馈,防止 React 更新占用掉一帧的所用时间

scheduleCallbackWithExpirationTime 异步任务调度

requestWork 中中如果判断是异步调度的方法就会执行 scheduleCallbackWithExpirationTime

// requestWork
function requestWork(root: FiberRoot, expirationTime: ExpirationTime) {
  // ...
  if (expirationTime === Sync) { // 同步的调用 js 代码
    performSyncWork();
  } else { // 异步调度 独立的 react 模块包,利用浏览器有空闲的时候进行执行,设置 deadline 在此之前执行
    scheduleCallbackWithExpirationTime(root, expirationTime); // 在 secheduler 文件夹下的单独模块
  }
}


function scheduleCallbackWithExpirationTime(
  root: FiberRoot,
  expirationTime: ExpirationTime,
) {
  // callbackExpirationTime 是上一次调度的任务优先级
  if (callbackExpirationTime !== NoWork) {
    // A callback is already scheduled. Check its expiration time (timeout).
    // 当前优先级比之前正在执行的优先级低就停止
    if (expirationTime > callbackExpirationTime) {
      // Existing callback has sufficient timeout. Exit.
      return;
    } else {
      //  当前优先级更高 则取消原来那个
      if (callbackID !== null) {
        // Existing callback has insufficient timeout. Cancel and schedule a
        // new one.
        cancelDeferredCallback(callbackID);
      }
    }
    // The request callback timer is already running. Don't start a new one.
  } else {
    startRequestCallbackTimer();
  }

  // 保存当前任务优先级
  callbackExpirationTime = expirationTime;
  // originalStartTimeMs 是 react 加载的最初时间, 记录当前时间差
  const currentMs = now() - originalStartTimeMs;
  // expirationTime(到期时间为将来的某个时间)转化成 ms
  const expirationTimeMs = expirationTimeToMs(expirationTime);
  // 当前任务的过期时间, 到期时间减去当前任务开始调度的时间差,则为过期时间,如果timeout < 0 ,则表示任务过期,需要强制更新
  const timeout = expirationTimeMs - currentMs;
  // 依赖 Scheduler 模块返回的id用来cancel
  callbackID = scheduleDeferredCallback(performAsyncWork, {timeout});
}

scheduleCallback

scheduler 模块下 unstable_scheduleCallback 函数

// ReactDOMHostConfig.js

export {
  unstable_now as now,
  unstable_scheduleCallback as scheduleDeferredCallback,
  unstable_cancelCallback as cancelDeferredCallback,
} from 'scheduler';
  • firstCallbackNode 是维护的双向链表结构的头部
  • 将新的任务的优先级与 firstCallbackNode 的优先级进行排序,保证firstCallbackNode 始终是优先级最高的
  • 当 firstCallbackNode 链表的首个任务改变时调用 ensureHostCallbackIsScheduled 进行调度,firstCallbackNode 没改变按原来的优先级执行
// callback -> performAsyncWork, deprecated_options -> { timeout }
function unstable_scheduleCallback(callback, deprecated_options) {
  // 即 Date.now()
  var startTime =
    currentEventStartTime !== -1 ? currentEventStartTime : getCurrentTime();

  var expirationTime;
  if (
    typeof deprecated_options === 'object' &&
    deprecated_options !== null &&
    typeof deprecated_options.timeout === 'number'
  ) {
    // expirationTime 的逻辑将来可能全部移入到 Scheduler 包中, 目前只会进入这个判断
    // FIXME: Remove this branch once we lift expiration times out of React.
    expirationTime = startTime + deprecated_options.timeout;
  } else {
    switch (currentPriorityLevel) {
      case ImmediatePriority:
        expirationTime = startTime + IMMEDIATE_PRIORITY_TIMEOUT;
        break;
      case UserBlockingPriority:
        expirationTime = startTime + USER_BLOCKING_PRIORITY;
        break;
      case IdlePriority:
        expirationTime = startTime + IDLE_PRIORITY;
        break;
      case NormalPriority:
      default:
        expirationTime = startTime + NORMAL_PRIORITY_TIMEOUT;
    }
  }

  var newNode = {
    // performAsyncWork
    callback,
    // 目前用不到
    priorityLevel: currentPriorityLevel,
    // timeout + now()
    expirationTime,
    // 存储链表结构
    next: null,
    previous: null,
  };

  // firstCallbackNode 是用来维护的双向链表的头部
  if (firstCallbackNode === null) {
    // This is the first callback in the list.
    firstCallbackNode = newNode.next = newNode.previous = newNode;
    // firstCallbackNode 改变需要调用  进入调度过程
    ensureHostCallbackIsScheduled();
  } else {
    // 如果有一个/多个 node 存在链表结构里
    // next表示下一个要执行的node
    var next = null;
    // 原来的
    var node = firstCallbackNode;
    do {
      // 根据 expirationTime 排序,把优先级高的放在前面,每次都跟 firstCallbackNode 比较
      if (node.expirationTime > expirationTime) {
        // The new callback expires before this one.
        // 表示新的node的优先级高于原来的,将原来的(firstCallbackNode)赋值给 next, 退出循环
        next = node;
        break;
      }
      node = node.next;
    } while (node !== firstCallbackNode);

    // 如果 nex t为null,说明 node.expirationTime < expirationTime, 即新的node的优先级是最小的
    if (next === null) {
      // No callback with a later expiration was found, which means the new
      // callback has the latest expiration in the list.
      // next为原来的node
      next = firstCallbackNode;
    // 表示新的node的优先级 高于原来node的优先级,
    } else if (next === firstCallbackNode) {
      // The new callback has the earliest expiration in the entire list.
      // 重新将 firstCallbackNode 赋值为新的node,即保证firstCallbackNode 为优先级最高的那个node
      firstCallbackNode = newNode;
      // firstCallbackNode 改变需要调用  进入调度过程
      ensureHostCallbackIsScheduled();
    }

    // 原来 指向firstCallBakcNode.previous 和 指向firstCallBakcNode.next 都指向自己
    // previous、next 都是指向firstCallBakcNode
    var previous = next.previous;
    // 现在将 都是指向firstCallBakcNode.previous 和 都是指向firstCallBakcNode.next 指向 newNode
    previous.next = next.previous = newNode;

    // 将 newNode.previous 和newNode.next 指向firstCallBakcNode, 形成环状双向链表结构
    newNode.next = next;
    newNode.previous = previous;

  }

  return newNode;
}

ensureHostCallbackIsScheduled

  • 检查是否有 callbackNode 在执行,否则停止执行
  • 判断 hostCallback 是否在调度,已经调度就取消
  • 执行 requestHostCallback
function ensureHostCallbackIsScheduled() {
  // 表示已经有一个 callbackNode 在调用了
  if (isExecutingCallback) {
    // Don't schedule work yet; wait until the next time we yield.
    return;
  }
  // Schedule the host callback using the earliest expiration in the list.
  var expirationTime = firstCallbackNode.expirationTime;
  // 判断这个 hostCallback 有没有进入调度
  if (!isHostCallbackScheduled) {
    isHostCallbackScheduled = true;
  } else {
    // Cancel the existing host callback.
    cancelHostCallback();
  }
  requestHostCallback(flushWork, expirationTime);
}

requestHostCallback

// callback 为 flushWork 函数
requestHostCallback = function(callback, absoluteTimeout) {
    scheduledHostCallback = callback;
    timeoutTime = absoluteTimeout;
    // absoluteTimeout < 0 已经超时,直接强制执行
    if (isFlushingHostCallback || absoluteTimeout < 0) {
      // Don't wait for the next frame. Continue working ASAP, in a new event.
      window.postMessage(messageKey, '*');
      // isAnimationFrameScheduled = false 表示还没进入调度循环
    } else if (!isAnimationFrameScheduled) {
      // If rAF didn't already schedule one, we need to schedule a frame.
      // TODO: If this rAF doesn't materialize because the browser throttles, we
      // might want to still have setTimeout trigger rIC as a backup to ensure
      // that we keep performing work.
      isAnimationFrameScheduled = true;
      // 进入调度, 竞争调用 animationTick
      requestAnimationFrameWithTimeout(animationTick);
    }
  };

requestAnimationFrameWithTimeout

  • 100ms 内竞争调用 requestAnimationFrame 和 setTimeout
  • 把 animationTick 加入到浏览器动画 requestAnimationFrame 回调函数里,如果 100ms 内还未执行就取消 requestAnimationFrame, 通过 setTimeout 自动执行
var ANIMATION_FRAME_TIMEOUT = 100;
var rAFID;
var rAFTimeoutID;
var requestAnimationFrameWithTimeout = function(callback) {
  // schedule rAF and also a setTimeout
  // requestAnimationFrame 执行则取消 setTimeout
  rAFID = localRequestAnimationFrame(function(timestamp) {
    // cancel the setTimeout
    localClearTimeout(rAFTimeoutID);
    callback(timestamp);
  });
  // 100ms 内 requestAnimationFrame 内的 callback 还未执行,则取消requestAnimationFrame,
  rAFTimeoutID = localSetTimeout(function() {
    // cancel the requestAnimationFrame
    localCancelAnimationFrame(rAFID);
    // 传入当前时间
    callback(getCurrentTime());
  }, ANIMATION_FRAME_TIMEOUT);
};

animationTick

  var frameDeadline = 0;
  // We start out assuming that we run at 30fps but then the heuristic tracking
  // will adjust this value to a faster fps if we get more frequent animation
  // frames.
  // 假设以 30fps 运行,然后进行跟踪,如果获得更频繁的帧,则会将此值调整为更快的fps
  var previousFrameTime = 33;
  var activeFrameTime = 33;

var animationTick = function(rafTime) {
    if (scheduledHostCallback !== null) {
      // 立马请求下一帧调用自己,不停的调用,队列里有很多 callback
      requestAnimationFrameWithTimeout(animationTick);
    } else {
      // No pending work. Exit.
      // 没有方法要被调度,返回
      isAnimationFrameScheduled = false;
      return;
    }
    //  rafTime 是调用的当前时间,frameDeadline 为0,activeFrameTime 为 33,即保持浏览器一秒 30帧,每一帧的执行一帧完整的时间
    // 用来计算下一帧可以执行的时间是多少,即nextFrameTime
    var nextFrameTime = rafTime - frameDeadline + activeFrameTime;
    // 下一个方法调用进来 下一帧重新计算nextFrameTime,rafTime - frameDeadline 如果小于 0 ,说明浏览器的刷新频率要大于 30hz,帧时间是要小于 33ms的
    // 如果连续两次帧的调用计算出来的时间是小于 33ms,就设置帧时间变小,
    // 主要针对不同平台的刷新频率的问题,比如 vr 120帧,根据平台的刷新频率设置 activeFrameTime, 1000 / 120 -> 8
    if (
      nextFrameTime < activeFrameTime &&
      previousFrameTime < activeFrameTime
    ) {
      if (nextFrameTime < 8) {
        // Defensive coding. We don't support higher frame rates than 120hz.
        // If the calculated frame time gets lower than 8, it is probably a bug.
        // 每帧最小运行时间
        nextFrameTime = 8;
      }
      // If one frame goes long, then the next one can be short to catch up.
      // If two frames are short in a row, then that's an indication that we
      // actually have a higher frame rate than what we're currently optimizing.
      // We adjust our heuristic dynamically accordingly. For example, if we're
      // running on 120hz display or 90hz VR display.
      // Take the max of the two in case one of them was an anomaly due to
      // missed frame deadlines.
      // 通过前后帧时间的判断,来判断平台的刷新频率,然后更新 activeFrameTime, 来达到减少React运行占用时间的目的
      activeFrameTime =
        nextFrameTime < previousFrameTime ? previousFrameTime : nextFrameTime;
    } else {
      // 前一帧运行时间和下一帧运行时间相同
      previousFrameTime = nextFrameTime;
    }
    // 第一个 frameDeadline
    frameDeadline = rafTime + activeFrameTime;
    // isMessageEventScheduled 会在 cancelHostCallback 和 idleTick 里都会设置为 false
    if (!isMessageEventScheduled) {
      isMessageEventScheduled = true;
      // 通过 requestAnimationFrame 调用完 callback 后立马会进入浏览器动画更新的设定,往任务队列里插入react任务来模拟 requestIdleCallback 方法
      // postMessage属于 macrotask,等待浏览器执行完再执行队列里的任务,即使每一帧的时间为33ms,但等到 message 接收时,实际留给react的时间没有 33ms
      // 给任务队列插入 react 任务,等浏览器执行完自己的任务再执行这里队列里的
      window.postMessage(messageKey, '*');
    }
  };

idleTick

  • 判断 postMessage 任务
  • 判断当前帧是否把时间用完,帧时间用完且任务过期了,didTimeout 为true,标识过期,准备强制更新;任务没有过期,则退出而不执行callback
  • 帧时间没有用完,则didTimeout 为false,执行callback
  • 判断 callback 不为空,则调用 react 任务
  • 这个方法保证了用户输入或动画最大时间限度执行,react的更新任务只能在 33ms内还剩省时间或者强制更新。(超过33ms的未过期的任务继续等待)
// 接受 react 任务
window.addEventListener('message', idleTick, false);

var idleTick = function(event) {
    // 判断 key 和 判断 message 源
    if (event.source !== window || event.data !== messageKey) {
      return;
    }

    isMessageEventScheduled = false;

    // 先赋值 再重置,
    // scheduledHostCallback -> flushWork
    // timeoutTime -> firstCallbackNode.expirationTime 到期时间
    var prevScheduledCallback = scheduledHostCallback;
    var prevTimeoutTime = timeoutTime;
    scheduledHostCallback = null;
    timeoutTime = -1;

    var currentTime = getCurrentTime();

    var didTimeout = false;
    // 表示浏览器动画或用户反馈操作超过 33ms, 把这一帧的时间用完了,react 没有时间去更新了
    if (frameDeadline - currentTime <= 0) {
      // There's no time left in this idle period. Check if the callback has
      // a timeout and whether it's been exceeded.
      // 表示当前任务也已经过期
      if (prevTimeoutTime !== -1 && prevTimeoutTime <= currentTime) {
        // Exceeded the timeout. Invoke the callback even though there's no
        // time left.
        // 标识已过期,准备强制更新
        didTimeout = true;
      } else {
        // No timeout.
        // 没有过期,且 isAnimationFrameScheduled 为 false 的情况,则直接调用 requestAnimationFrameWithTimeout
        // 在 animationTick 函数中 判断 scheduledHostCallback === null 时才会赋值其为false,
        // 而 scheduledHostCallback === null 只有在 cancelHostCallback 函数 和 idleTick 函数中执行
        if (!isAnimationFrameScheduled) {
          // Schedule another animation callback so we retry later.
          // 调度另一个动画回调,以便我们稍后重试
          isAnimationFrameScheduled = true;
          requestAnimationFrameWithTimeout(animationTick);
        }
        // Exit without invoking the callback.
        // 没有过期,则退出而不执行callback,将scheduledHostCallback、tiemoutTime 恢复原来值,直接return
        scheduledHostCallback = prevScheduledCallback;
        timeoutTime = prevTimeoutTime;
        return;
      }
    }

    if (prevScheduledCallback !== null) {
      // 正在调用这个 callback
      isFlushingHostCallback = true;
      try {
        // 根据 didTimeout 判断 是否强制执行更新
        prevScheduledCallback(didTimeout);
      } finally {
        isFlushingHostCallback = false;
      }
    }
  };

flushWork

  • callback 已经过期,则循环判断链表(更改firstCallbackNode)是否已过期,直到第一个没有过期的任务为止,并将已过期的任务都强制输出
  • callback 没有过期,则循环判断是否还有空余时间执行 callback,有空余时间则执行
  • 最后调用 cancelHostCallback 重置所有调度变量,其中 scheduledHostCallback重置为null 避免重复执行老的callback
var deadlineObject = {
  timeRemaining,
  didTimeout: false,
};
// 默认 hasNativePerformanceNow = true
var timeRemaining = function() {
    // ...
    // We assume that if we have a performance timer that the rAF callback
    // gets a performance timer value. Not sure if this is always true.
    // frameDeadline - now() > 0  这一帧的渲染剩余多少时间, 在 shouldYield 中使用并判断
    // 模拟传给 requestIdleCallback方法的回调函数的 IdleDeadline 参数 https://developer.mozilla.org/zh-CN/docs/Web/API/IdleDeadline 表示当前闲置周期的预估剩余毫秒数

    var remaining = getFrameDeadline() - performance.now();
    return remaining > 0 ? remaining : 0;
}

// didTimout -> firstCallbackNode 是否已经超时的判断
function flushWork(didTimeout) {
  // 开始调用 callback -> ensureHostCallbackIsScheduled 函数会直接return
  isExecutingCallback = true;
  deadlineObject.didTimeout = didTimeout;
  try {
    // firstCallbackNode的任务已经过期
    if (didTimeout) {
      // Flush all the expired callbacks without yielding.
      // 循环
      while (firstCallbackNode !== null) {
        // Read the current time. Flush all the callbacks that expire at or
        // earlier than that time. Then read the current time again and repeat.
        // This optimizes for as few performance.now calls as possible.
        var currentTime = getCurrentTime();
        // 第一个 expirationTime 肯定小于 currentTime, 为过期任务
        if (firstCallbackNode.expirationTime <= currentTime) {
          // 循环执行callback链表,直到第一个没有过期的任务为止, 已经过期的任务都强制输出
          do {
            // flushFirstCallback 会将 firstCallbackNode.next 赋值给 firstCallbackNode
            flushFirstCallback();
          } while (
            // firstCallbackNode 变成下一个,循环判断链表任务是否过期
            firstCallbackNode !== null &&
            firstCallbackNode.expirationTime <= currentTime
          );
          // 已经过期的任务都输出了,下一次判断if (firstCallbackNode.expirationTime <= currentTime) 为false,直接break
          continue;
        }
        break;
      }
    } else {
      // Keep flushing callbacks until we run out of time in the frame.
      // 任务没有过期, 继续刷新回调,直到帧中的时间不足为止。
      if (firstCallbackNode !== null) {
        do {
          flushFirstCallback();
        } while (
          // 帧还有时间空闲(还有剩余时间)才执行 callback
          firstCallbackNode !== null &&
          getFrameDeadline() - getCurrentTime() > 0
        );
      }
    }
  } finally {
    isExecutingCallback = false;
    if (firstCallbackNode !== null) {
      // There's still work remaining. Request another callback.
      // 还剩有 firsrCallbackNode 时,调用ensureHostCallbackIsScheduled ,此时 isHostCallbackScheduled 为 true,则会执行 cancelHostCallback 重置所有的调度常量,将 scheduledHostCallback重置为null,避免在 animationTick 中将老的 callback 重复执行一边
      ensureHostCallbackIsScheduled();
    } else {
      isHostCallbackScheduled = false;
    }
    // Before exiting, flush all the immediate work that was scheduled.
    // firstCallbackNode.priorityLevel === currentPriorityLevel 这个函数不会执行
    flushImmediateWork();
  }
}

flushFirstCallback

  • 将 firstCallbackNode 指向它的next处理链表表头,执行callback
function flushFirstCallback() {
  var flushedNode = firstCallbackNode;

  // Remove the node from the list before calling the callback. That way the
  // list is in a consistent state even if the callback throws.
  var next = firstCallbackNode.next;
  // 表示只有一个 callbackNode
  if (firstCallbackNode === next) {
    // This is the last callback in the list.
    firstCallbackNode = null;
    next = null;
  } else {
    // 替换 firstCallbackNode 的操作
    var lastCallbackNode = firstCallbackNode.previous;
    // 将 firstCallbackNode 指向 firstCallbackNode.next
    firstCallbackNode = lastCallbackNode.next = next;
    next.previous = lastCallbackNode;
  }

  // 清空老 firstCallbackNode 的 next 和 previous
  flushedNode.next = flushedNode.previous = null;

  // Now it's safe to call the callback.
  var callback = flushedNode.callback;
  var expirationTime = flushedNode.expirationTime;
  var priorityLevel = flushedNode.priorityLevel;
  var previousPriorityLevel = currentPriorityLevel;
  var previousExpirationTime = currentExpirationTime;
  currentPriorityLevel = priorityLevel;
  currentExpirationTime = expirationTime;
  var continuationCallback;
  try {
    // 执行 callback
    continuationCallback = callback(deadlineObject);
  } finally {
    currentPriorityLevel = previousPriorityLevel;
    currentExpirationTime = previousExpirationTime;
  }

  // performAsyncWork 没有返回则下面不会执行
   if (typeof continuationCallback === 'function') {
     // ....
   }
}

Hooks

Hoooks

定义

  • 通过全局变量 ReactCurrentOwnercurrentDispatcher 属性挂载 hooks api
// ReactHooks.js

import ReactCurrentOwner from './ReactCurrentOwner';

function resolveDispatcher() {
  const dispatcher = ReactCurrentOwner.currentDispatcher;
  invariant(
    dispatcher !== null,
    'Hooks can only be called inside the body of a function component.',
  );
  return dispatcher;
}

// ...
function useState<S>(initialState: (() => S) | S) {
  const dispatcher = resolveDispatcher();
  return dispatcher.useState(initialState);
}

function useReducer<S, A>(
  reducer: (S, A) => S,
  initialState: S,
  initialAction: A | void | null,
) {
  const dispatcher = resolveDispatcher();
  return dispatcher.useReducer(reducer, initialState, initialAction);
}

// ...
  • 真正赋值 ReactCurrentOwner.currentDispatcher 是在 renderRoot 的开始时时候为赋值为 Dispatcher,结束 workLoop 后赋值为 null.即只有在 workLoop 阶段才会有值。
// ReactFiberScheduler.js

function renderRoot(root: FiberRoot, isYieldy: boolean): void {
  
  flushPassiveEffects();

  isWorking = true;
  if (enableHooks) {
    ReactCurrentOwner.currentDispatcher = Dispatcher;
  } else {
    ReactCurrentOwner.currentDispatcher = DispatcherWithoutHooks;
  }
  // ....

  // ...
  // We're done performing work. Time to clean up.
  isWorking = false;
  ReactCurrentOwner.currentDispatcher = null;
  // ...
}

更新阶段调用 hooks 过程

  • beginWork 里执行 updateFunctionComponent、mountIndeterminateComponent、 updateForwardRef(即对应函数式组件)的更新,会先执行 prepareToUseHooks (和 context 类似执行 prepareToReadContext), 在 render 完成后,执行 finishHooks.
function updateFunctionComponent(
 current,
 workInProgress,
 Component,
 nextProps: any,
 renderExpirationTime,
) {
 const unmaskedContext = getUnmaskedContext(workInProgress, Component, true);
 const context = getMaskedContext(workInProgress, unmaskedContext);

 let nextChildren;
 prepareToReadContext(workInProgress, renderExpirationTime);
 // hooks 方式写的组件是可以读取 context 的,且可以读取多个 context 的,即为什么 prepareToReadContext 会在 fiber 节点上使用链表方式 currentlyRenderingFiber.firstContextDependency = lastContextDependency = contextItem; 去记录 context的原因
 prepareToUseHooks(current, workInProgress, renderExpirationTime);
 if (__DEV__) {
   ReactCurrentOwner.current = workInProgress;
   setCurrentPhase('render');
   nextChildren = Component(nextProps, context);
   setCurrentPhase(null);
 } else {
   nextChildren = Component(nextProps, context);
 }
 // 
 nextChildren = finishHooks(Component, nextProps, nextChildren, context);

 // React DevTools reads this flag.
 workInProgress.effectTag |= PerformedWork;
 reconcileChildren(
   current,
   workInProgress,
   nextChildren,
   renderExpirationTime,
 );
 return workInProgress.child;
}

prepareToUseHooks

  • 初始化模块当中的全局变量
function prepareToUseHooks(
  current: Fiber | null,
  workInProgress: Fiber,
  nextRenderExpirationTime: ExpirationTime,
): void {
  if (!enableHooks) {
    return;
  }
  // 当前更新的优先级
  renderExpirationTime = nextRenderExpirationTime;
  // 当前更新的 fiber 
  currentlyRenderingFiber = workInProgress;
  // 非首次渲染,memoizedState 记录的是 functionComponent当中第一个 hooks api 对应的 hook 对象
  firstCurrentHook = current !== null ? current.memoizedState : null;

}

finishHooks

  • didScheduleRenderPhaseUpdate 表示在渲染过程中产生的 update。即在 functionComponentreturn 之前调用了 useState 返回的 setXXX 则这个变量被赋值为 true,目的是让在渲染过程中产生的 update 经过 while 渲染判断在一次渲染过程中就被执行掉,而不是等到下一次更新中执行
  • 每一个 hook api 都会对应一个 hook 对象,挂载在 functionComponent 对应的 fiber 节点上的 memoizedState 属性上
function finishHooks(
  Component: any,
  props: any,
  children: any,
  refOrContext: any,
): any {
  if (!enableHooks) {
    return children;
  }

  // This must be called after every function component to prevent hooks from
  // being used in classes.

  // 如果是重复渲染的情况,清空全局变量 currentHook workInProgressHook,并计数 numberOfReRenders 渲染次数,在 dispatchAction 会 判断次数如果大于 RE_RENDER_LIMIT 抛出错误
  // 再执行一次 Component(props, refOrContext); 将 重复渲染在一次 更新中执行掉
  while (didScheduleRenderPhaseUpdate) {
    // Updates were scheduled during the render phase. They are stored in
    // the `renderPhaseUpdates` map. Call the component again, reusing the
    // work-in-progress hooks and applying the additional updates on top. Keep
    // restarting until no more updates are scheduled.
    didScheduleRenderPhaseUpdate = false;
    numberOfReRenders += 1;

    // Start over from the beginning of the list
    currentHook = null;
    workInProgressHook = null;
    componentUpdateQueue = null;
    
    children = Component(props, refOrContext);
  }
  renderPhaseUpdates = null;
  numberOfReRenders = 0;

  // 更新中的 fiber 对象
  const renderedWork: Fiber = (currentlyRenderingFiber: any);
  // firstWorkInProgressHook 是 functionComponent 每一个 hooks 对应的 hook 对象。
  renderedWork.memoizedState = firstWorkInProgressHook;
  renderedWork.expirationTime = remainingExpirationTime;
 // 对于useEffect 或者 useLayoutEffect 生成的 effect 链 
  renderedWork.updateQueue = (componentUpdateQueue: any);

  const didRenderTooFewHooks =
    currentHook !== null && currentHook.next !== null;

  renderExpirationTime = NoWork;
  currentlyRenderingFiber = null;

  firstCurrentHook = null;
  currentHook = null;
  firstWorkInProgressHook = null;
  workInProgressHook = null;

  remainingExpirationTime = NoWork;
  componentUpdateQueue = null;

  // Always set during createWorkInProgress
  // isReRender = false;

  // These were reset above
  // didScheduleRenderPhaseUpdate = false;
  // renderPhaseUpdates = null;
  // numberOfReRenders = 0;

  return children;
}

useState

  • 实际使用的是 useReducer
function useState<S>(
  initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
  return useReducer(
    basicStateReducer,
    // useReducer has a special case to support lazy useState initializers
    (initialState: any),
  );
}

function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {
  return typeof action === 'function' ? action(state) : action;
}

useReducer

  • 在每个 hooks api 调用时会调用 createWorkInProgressHook 创建一个 hook 对象并赋值给全局变量 workInProgressHook
  • createWorkInProgressHook 内部 hook 对象构建成链表结构
function useReducer<S, A>(
  reducer: (S, A) => S,
  initialState: S,
  initialAction: A | void | null,
): [S, Dispatch<A>] {
  // 找到正在执行更新的 fiber 
  currentlyRenderingFiber = resolveCurrentlyRenderingFiber();
  // 创建一个 hook 对象, 并通过全局变量构建 hook 链表
  workInProgressHook = createWorkInProgressHook();
  // 非首次渲染, queue 不为空的情况
  let queue: UpdateQueue<A> | null = (workInProgressHook.queue: any);
  if (queue !== null) {
    // Already have a queue, so this is an update.
    // 重复渲染
    if (isReRender) {
      // This is a re-render. Apply the new render phase updates to the previous
      // work-in-progress hook.
      const dispatch: Dispatch<A> = (queue.dispatch: any);
      if (renderPhaseUpdates !== null) {
        // Render phase updates are stored in a map of queue -> linked list
        // renderPhaseUpdates 在 dispatchAction 被设置已 queque 为 key 的 map对象
        const firstRenderPhaseUpdate = renderPhaseUpdates.get(queue);
        if (firstRenderPhaseUpdate !== undefined) {
          // 取出来后 删掉 queue
          renderPhaseUpdates.delete(queue);
          let newState = workInProgressHook.memoizedState;
          let update = firstRenderPhaseUpdate;
          // 重复渲染的过程中 所有update 立马执行,不进行 expirationTime 的判断
          do {
            // Process this render phase update. We don't have to check the
            // priority because it will always be the same as the current
            // render's.
            const action = update.action;
            newState = reducer(newState, action);
            update = update.next;
          } while (update !== null);
          // 更新 memoizedState 
          workInProgressHook.memoizedState = newState;

          // Don't persist the state accumlated from the render phase updates to
          // the base state unless the queue is empty.
          // TODO: Not sure if this is the desired semantics, but it's what we
          // do for gDSFP. I can't remember why.
          if (workInProgressHook.baseUpdate === queue.last) {
            workInProgressHook.baseState = newState;
          }

          return [newState, dispatch];
        }
      }
      return [workInProgressHook.memoizedState, dispatch];
    }

    // The last update in the entire queue
    const last = queue.last;
    // The last update that is part of the base state.
    const baseUpdate = workInProgressHook.baseUpdate;

    // Find the first unprocessed update.
    let first;
    if (baseUpdate !== null) {
      if (last !== null) {
        // For the first update, the queue is a circular linked list where
        // `queue.last.next = queue.first`. Once the first update commits, and
        // the `baseUpdate` is no longer empty, we can unravel the list.
        last.next = null;
      }
      first = baseUpdate.next;
    } else {
      // 获取第一个  update 
      first = last !== null ? last.next : null;
    }
    if (first !== null) {
      // 原来的老的值
      let newState = workInProgressHook.baseState;
      let newBaseState = null;
      let newBaseUpdate = null;
      let prevUpdate = baseUpdate;
      let update = first;
      let didSkip = false;
      // 将 update 链循环执行完
      do {
        const updateExpirationTime = update.expirationTime;
        // hooks 之后的expirationTime的计算大小颠倒,越大表示优先级越高
        if (updateExpirationTime < renderExpirationTime) {
          // 更新优先级低
          // Priority is insufficient. Skip this update. If this is the first
          // skipped update, the previous update/state is the new base
          // update/state.
          // 只有有第一个在当前更新中更新优先级小于renderExpirationTime的(需要跳过的),那么 didSkip 会被设置为 true
          if (!didSkip) {
            didSkip = true;
            // 记录的是这个需要被跳过的 update 的前一个 update
            newBaseUpdate = prevUpdate;
            // 前一个 state  
            newBaseState = newState;
          }
          // Update the remaining priority in the queue.
          // remainingExpirationTime 初始值为 0
          // 只要有一个被跳过,则赋值为 跳过的update的对应的 expirationTime
          // 最后被设置为需要跳过的 update 里优先级最大的 expirationTime
          // 最后在 finishHooks  中会被设置为 fiber 对象的 expirationTime,即尚未执行更新的 update 里优先级最高的 expirationTime
          if (updateExpirationTime > remainingExpirationTime) {
            remainingExpirationTime = updateExpirationTime;
          }
        } else {
          // Process this update.
          const action = update.action;
          // 对于 useState 来说 action 就是更新后的值,而非 function
          // 即 newState = action 赋值新值
          newState = reducer(newState, action);
        }
        prevUpdate = update;
        update = update.next;
      } while (update !== null && update !== first);

      //  如果没有跳过的
      if (!didSkip) {
        // prevUpdate 为最后一个 update 即 last  
        newBaseUpdate = prevUpdate;
        newBaseState = newState;
      }
      // memoizedState 更新为新的 state
      workInProgressHook.memoizedState = newState;
      // 更新 baseUpdate, baseUpdate 记录的要么是 last 要么是需要跳过的update 的前一个 update 
      //  即在开头判断 baseUpdate  如果不为 null,则从 baseUpdate.next 开始更新
      workInProgressHook.baseUpdate = newBaseUpdate;
      workInProgressHook.baseState = newBaseState;
    }

    const dispatch: Dispatch<A> = (queue.dispatch: any);
    return [workInProgressHook.memoizedState, dispatch];
  }

  // 首次渲染, hook.queue 为空
  // There's no existing queue, so this is the initial render.
  if (reducer === basicStateReducer) {
    // useState 的情况  
    // Special case for `useState`.
    if (typeof initialState === 'function') {
      initialState = initialState();
    }
  } else if (initialAction !== undefined && initialAction !== null) {
    // useReducer 接受的第三个参数,用来计算 initialState
    initialState = reducer(initialState, initialAction);
  }
  // 将 initialState 挂载到 hook 对象上的 memoizedState 及 baseState,即使得 functionComponent 也有读取 state 的能力
  // hook 对象是更细粒度的 fiber
  workInProgressHook.memoizedState = workInProgressHook.baseState = initialState;
  // 挂载 queue,last 用来记录 dispatchAction 函数中生成的 update 链
  queue = workInProgressHook.queue = {
    last: null,
    dispatch: null,
  };
  // 生成dispatch函数 
  const dispatch: Dispatch<A> = (queue.dispatch = (dispatchAction.bind(
    null,
    currentlyRenderingFiber,
    queue,
  ): any));
  return [workInProgressHook.memoizedState, dispatch];
}

createWorkInProgressHook

  • 创建 hook 对象,并构建链表结构
  • firstWorkInProgressHook 变量在这里被赋值为第一个创建的 hook,执行完 functionComponent 得到 children 后会走 finishHooks,将 firstWorkInProgressHook 赋值给 fiber.memoizedState,再将 firstWorkInProgressHook 清空
function createWorkInProgressHook(): Hook {
  if (workInProgressHook === null) {
    // This is the first hook in the list
    if (firstWorkInProgressHook === null) {
      // 都为 null,是非重复渲染的
      isReRender = false;
      currentHook = firstCurrentHook;
      if (currentHook === null) {
        // This is a newly mounted hook
        // 首次渲染,创建 hook 对象
        workInProgressHook = createHook();
      } else {
        // Clone the current hook.
        workInProgressHook = cloneHook(currentHook);
      }
      firstWorkInProgressHook = workInProgressHook;
    } else {
      // There's already a work-in-progress. Reuse it.
      // firstWorkInProgressHook 不为 null,说明 finishHooks 方法中的didScheduleRenderPhaseUpdate 为true 导致  while 循环在执行,即说明在重复渲染
      // 重复渲染的 
      isReRender = true;
      currentHook = firstCurrentHook;
      workInProgressHook = firstWorkInProgressHook;
    }
  } else {
    // 非首次渲染,并未执行过其他的 hooks api,也是非重复渲染的 
    if (workInProgressHook.next === null) {
      isReRender = false;
      let hook;
      if (currentHook === null) {
        // This is a newly mounted hook
        hook = createHook();
      } else {
        currentHook = currentHook.next;
        if (currentHook === null) {
          // This is a newly mounted hook
          hook = createHook();
        } else {
          // Clone the current hook.
          hook = cloneHook(currentHook);
        }
      }
      // 如果 next 为null 那么将 hook1.next = hook2 
      // Append to the end of the list
      workInProgressHook = workInProgressHook.next = hook;
    } else {
      // There's already a work-in-progress. Reuse it.
      // next 不为 null,复用原来的
      //  重复渲染
      isReRender = true;
      workInProgressHook = workInProgressHook.next;
      currentHook = currentHook !== null ? currentHook.next : null;
    }
  }
  return workInProgressHook;
}

// createHook

function createHook(): Hook {
  return {
    memoizedState: null,

    baseState: null,
    queue: null,
    baseUpdate: null,

    next: null,
  };
}

dispatchAction

  • 用来生成 dispatch 函数
  • 创建 update,并构建 update 的链表结构,将 queue.last 指向最后一个产生的 update
function dispatchAction<A>(fiber: Fiber, queue: UpdateQueue<A>, action: A) {
  // rerendered 次数 大于 RE_RENDER_LIMIT 则报错
  invariant(
    numberOfReRenders < RE_RENDER_LIMIT,
    'Too many re-renders. React limits the number of renders to prevent ' +
      'an infinite loop.',
  );

  const alternate = fiber.alternate;
  if (
    // 只有在 return 之前调用 setXXX, 即在更新当前 functionComponent 的中过程调用了 setState ,还未执行 finishHooks。 currentlyRenderingFiber => 当前 function 对应的 Fiber 
    fiber === currentlyRenderingFiber ||  
    (alternate !== null && alternate === currentlyRenderingFiber)
  ) {
    // This is a render phase update. Stash it in a lazily-created map of
    // queue -> linked list of updates. After this render pass, we'll restart
    // and apply the stashed updates on top of the work-in-progress hook.
    didScheduleRenderPhaseUpdate = true;
    const update: Update<A> = {
      expirationTime: renderExpirationTime,
      action,
      next: null,
    };
    // 将重复渲染的 update 收集, 在 useReducer 中会判断 renderPhaseUpdates 是否为空,并去 queue 对应的 update
    if (renderPhaseUpdates === null) {
      renderPhaseUpdates = new Map();
    }
    const firstRenderPhaseUpdate = renderPhaseUpdates.get(queue);
    if (firstRenderPhaseUpdate === undefined) {
      renderPhaseUpdates.set(queue, update);
    } else {
      // Append the update to the end of the list.
      let lastRenderPhaseUpdate = firstRenderPhaseUpdate;
      while (lastRenderPhaseUpdate.next !== null) {
        lastRenderPhaseUpdate = lastRenderPhaseUpdate.next;
      }
      // 重复渲染的 update 构建链表
      lastRenderPhaseUpdate.next = update;
    }
  } else {
    // 计算 expirationTime 
    const currentTime = requestCurrentTime();
    const expirationTime = computeExpirationForFiber(currentTime, fiber);
    // 创建 update 
    const update: Update<A> = {
      expirationTime,
      action,
      next: null,
    };
    // useEffect 相关
    flushPassiveEffects();
    // Append the update to the end of the list.
    // last 是 queue 用来标识最后一个 update 的
    // last.next 用来标识第一个 即 first update
    // 首次为 null, queue.last = update1, update1.next = update1
    const last = queue.last;
    if (last === null) {
      // This is the first update. Create a circular list.
      update.next = update;
    } else {
      // last 不为 null 即非首次
      // first -> update1
      const first = last.next;
      if (first !== null) {
        // Still circular.
        // 新的 update 即 update2.next = update1;
        update.next = first;
      }
      // update1.next = update2;
      last.next = update;
    }
    // last = update2;
    queue.last = update;
    // 加入调度
    scheduleWork(fiber, expirationTime);
  }
}

useEffect 与 useLayoutEffect

  • 都是执行的 useEffectImpl 不同的是前两个参数不同
    • 第一个参数为标记当前 fibereffectTag
    • 第二个参数标记的是 hook 对象的 effectTag
    • 第三个参数 create 为传入的匿名函数
    • 第四个参数 inputs 为依赖数组
  • useLayoutEffect 会在 commitRoot 的第三次循环 commitAllLifeCycles => commitLifeCycles 中被执行,执行时机相当于 componentDidMountcomponentDidUpdate
  • 在执行 commitAllLifeCycles 时会根据 nextEffect 是否有 Passive (passiveEffect)赋值 rootWithPendingPassiveEffects = finishedRoot 在 执行完 commitRoot 的三次循环后会判断这个变量如果存在则将 commitPassiveEffects 加入到 Schedule_scheduleCallback 异步任务调度中
  • useEffect 会在 commitPassiveEffects 被执行,先执行 destroy 再执行 create。加入调度任务中是为了给浏览器更多时间渲染,js执行更少的时间,防止页面阻塞。
// ReactHookEffectTags
const NoEffect = /*             */ 0b00000000;   // => NoHookEffect
const UnmountSnapshot = /*      */ 0b00000010;
const UnmountMutation = /*      */ 0b00000100;
const UnmountMutation = /*      */ 0b00000100;
const MountMutation = /*        */ 0b00001000;
const UnmountLayout = /*        */ 0b00010000;
const MountLayout = /*          */ 0b00100000;
const MountPassive = /*         */ 0b01000000;
const UnmountPassive = /*       */ 0b10000000;

// ReactSideEffectTags 
const Update = /*                */ 0b000000000100;   // => updateEffect
const Passive = /*               */ 0b001000000000;   //  => passiveEffect


function useLayoutEffect(
  create: () => mixed,
  inputs: Array<mixed> | void | null,
): void {
  
  useEffectImpl(
    UpdateEffect, 
    UnmountMutation | MountLayout,  // => 0b00100100
    create, 
    inputs
  );
}

export function useEffect(
  create: () => mixed,
  inputs: Array<mixed> | void | null,
): void {
  useEffectImpl(
    UpdateEffect | PassiveEffect, // => 0b001000000100
    UnmountPassive | MountPassive, // => 0b11000000
    create,
    inputs,
  );
}

useEffectImpl

  • fiber 标记对应的 effectTag
  • 调用 pushEffect 创建 hook 对象的 effect 并标记对应的 hookEffectTag,构建 effect 链赋值给 fiber 对象的 updateQueue
  • areHookInputsEqual 方法对比依赖数组,如果依赖变化则执行 pushEffect。即如果不传依赖数组,每次都会执行 pushEffect
function useEffectImpl(fiberEffectTag, hookEffectTag, create, inputs): void {
  currentlyRenderingFiber = resolveCurrentlyRenderingFiber();
  workInProgressHook = createWorkInProgressHook();

  // inputs 为传入的依赖数组,如果为空,则是回调函数自身
  let nextInputs = inputs !== undefined && inputs !== null ? inputs : [create];
  let destroy = null;
  // currentHook !== null 非首次渲染
  if (currentHook !== null) {
    const prevEffect = currentHook.memoizedState;
    destroy = prevEffect.destroy;
    // 前后对比依赖项是否变化
    if (areHookInputsEqual(nextInputs, prevEffect.inputs)) {
      // 如果相同
      pushEffect(NoHookEffect, create, destroy, nextInputs);
      return;
    }
  }
  // 第一次渲染 或者 依赖不相同  
  // 标记 fiber 的 effectTag 为 fiberEffectTag 
  currentlyRenderingFiber.effectTag |= fiberEffectTag;
  workInProgressHook.memoizedState = pushEffect(
    hookEffectTag,
    create,
    destroy,
    nextInputs,
  );
}

areHookInputsEqual

  • 对比依赖数组的每一项是否相同,都相同则返回 true 否则返回 false
  • Object.isPolyfill
function areHookInputsEqual(arr1: any[], arr2: any[]) {
  // Don't bother comparing lengths in prod because these arrays should be
  // passed inline.
  for (let i = 0; i < arr1.length; i++) {
    // Inlined Object.is polyfill.
    // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is
    const val1 = arr1[i];
    const val2 = arr2[i];
    if (
      (val1 === val2 && (val1 !== 0 || 1 / val1 === 1 / (val2: any))) ||
      (val1 !== val1 && val2 !== val2) // eslint-disable-line no-self-compare
    ) {
      continue;
    }
    return false;
  }
  return true;
}

pushEffect

  • 构建 functionComponent 所有的 effect 链,保存在 fiber 对象的 componentUpdateQueue 属性的 lastEffect
  • finishHooks 时将 componentUpdateQueue 赋值给 fiber.updateQueue
function pushEffect(tag, create, destroy, inputs) {
  const effect: Effect = {
    tag, //  => hookEffectTag
    create, 
    destroy,
    inputs,
    // Circular
    next: (null: any),
  };
  // 构建 functionComponent 所有的 effect  链
  if (componentUpdateQueue === null) {
    componentUpdateQueue = createFunctionComponentUpdateQueue();
    componentUpdateQueue.lastEffect = effect.next = effect;
  } else {
    const lastEffect = componentUpdateQueue.lastEffect;
    if (lastEffect === null) {
      componentUpdateQueue.lastEffect = effect.next = effect;
    } else {
      const firstEffect = lastEffect.next;
      lastEffect.next = effect;
      effect.next = firstEffect;
      componentUpdateQueue.lastEffect = effect;
    }
  }
  return effect;
}

function createFunctionComponentUpdateQueue(): FunctionComponentUpdateQueue {
  return {
    lastEffect: null,
  };
}

commitHookEffectList

  • commitRoot 的三次循环中均有被调用,三次循环后 还有一次调用是针对 useEffect
  • 在一次更新渲染中会先执行上一次 effectdestroy,即官方推荐不能在 hooks Api 之前写 if 条件判断是否使用 hooks,这样会下一次更新时构建的 effect 链的顺序是和上次不一样的,导致在 commitRoot 时顺序错乱。
function commitHookEffectList(
  unmountTag: number,
  mountTag: number,
  finishedWork: Fiber,
) {
  if (!enableHooks) {
    return;
  }
  // 读取 updateQueue
  const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any);
  let lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
  // 遍历在 useEffectImpl 中 pushEffect 生成的 effect 链 (hook对象的)
  if (lastEffect !== null) {
    const firstEffect = lastEffect.next;
    let effect = firstEffect;
    do {
      // 如果 effect.tag  有 unmountTag 则执行 destroy()
      if ((effect.tag & unmountTag) !== NoHookEffect) {
        // Unmount
        const destroy = effect.destroy;
        effect.destroy = null;
        if (destroy !== null) {
          destroy();
        }
      }
      // 如果 effect.tag 有 moountTag 则执行 create()
      if ((effect.tag & mountTag) !== NoHookEffect) {
        // Mount
        const create = effect.create;

        let destroy = create();
        if (typeof destroy !== 'function') {
          // 非函数返回的提示
          if (__DEV__) {
            if (destroy !== null && destroy !== undefined) {
              warningWithoutStack(
                false,
                'useEffect function must return a cleanup function or ' +
                  'nothing.%s%s',
                typeof destroy.then === 'function'
                  ? '\n\nIt looks like you wrote useEffect(async () => ...) or returned a Promise. ' +
                    'Instead, you may write an async function separately ' +
                    'and then call it from inside the effect:\n\n' +
                    'async function fetchComment(commentId) {\n' +
                    '  // You can await here\n' +
                    '}\n\n' +
                    'useEffect(() => {\n' +
                    '  fetchComment(commentId);\n' +
                    '}, [commentId]);\n\n' +
                    'In the future, React will provide a more idiomatic solution for data fetching ' +
                    "that doesn't involve writing effects manually."
                  : '',
                getStackByFiberInDevAndProd(finishedWork),
              );
            }
          }
          destroy = null;
        }
        effect.destroy = destroy;
      }
      effect = effect.next;
    } while (effect !== firstEffect);
  }
}

第一次循环 commitBeforeMutationLifecycles

  • unmountTag -> UnmountSnapshot。 在 useEffectImpl 没有推入 UnmountSnapshot。 因此不会执行 destroy
  • mountTag -> NoHookEffect。 也不会执行 create
function commitBeforeMutationLifeCycles(
  current: Fiber | null,
  finishedWork: Fiber,
): void {
  switch (finishedWork.tag) {
    case FunctionComponent:
    case ForwardRef:
    case SimpleMemoComponent: {
      commitHookEffectList(UnmountSnapshot, NoHookEffect, finishedWork);
      return;
    }
    //...
  }
  //...
}

第二次循环 commitAllHostEffects

  • commitAllHostEffectsdom 节点的 PlacementAndUpdateUpdate 会先执行 commitWork
  • unmountTag -> UnmountMutation 对应 useLayoutEffect 传入 UnmountMutation | MountLayout,因此会执行 useLayoutEffectdestroy
  • mountTag -> MountMutation。没有标记 MountMutation 因此都不会执行 create
function commitWork(current: Fiber | null, finishedWork: Fiber): void {
  // ...
  switch (finishedWork.tag) {
    case FunctionComponent:
    case ForwardRef:
    case MemoComponent:
    case SimpleMemoComponent: {
      // Note: We currently never use MountMutation, but useLayout uses
      // UnmountMutation.
      commitHookEffectList(UnmountMutation, MountMutation, finishedWork);
      return;
    }
    // ...
  }
}

第三次循环 commitAllLifeCycles

  • commitAllLifeCycles -> commitLifeCycles
  • unmountTag -> UnmountLayout. 都没有标记 UnmountLayout 都不会执行 destroy
  • mountTag -> MountLayout. useLayoutEffect 标记了 MountLayout 因此 会执行它的 create 方法,对应 classComponent 组件会在这里执行 componentDidMountcomponentDidUpdate
  • 执行完 commitLifeCycles 之后,如果 effectTagPassive 则赋值 rootWithPendingPassiveEffects = finishedRoot。为 useEffect 执行做准备
function commitAllLifeCycles(
  finishedRoot: FiberRoot,
  committedExpirationTime: ExpirationTime,
) {
  if (__DEV__) {
    ReactStrictModeWarnings.flushPendingUnsafeLifecycleWarnings();
    ReactStrictModeWarnings.flushLegacyContextWarning();

    if (warnAboutDeprecatedLifecycles) {
      ReactStrictModeWarnings.flushPendingDeprecationWarnings();
    }
  }
  while (nextEffect !== null) {
    const effectTag = nextEffect.effectTag;

    if (effectTag & (Update | Callback)) {
      recordEffect();
      const current = nextEffect.alternate;
      commitLifeCycles(
        finishedRoot,
        current,
        nextEffect,
        committedExpirationTime,
      );
    }

    if (effectTag & Ref) {
      recordEffect();
      commitAttachRef(nextEffect);
    }

    // useEffect 中标记 fiber 的 effectTag 为 updateEffect | passiveEffect 
    // 因此对于 useEffect 的节点会满足条件,从而赋值 rootWithPendingPassiveEffects
    if (enableHooks && effectTag & Passive) {
      rootWithPendingPassiveEffects = finishedRoot;
    }

    nextEffect = nextEffect.nextEffect;
  }
}

function commitLifeCycles(
  finishedRoot: FiberRoot,
  current: Fiber | null,
  finishedWork: Fiber,
  committedExpirationTime: ExpirationTime,
): void {
  switch (finishedWork.tag) {
    case FunctionComponent:
    case ForwardRef:
    case SimpleMemoComponent: {
      commitHookEffectList(UnmountLayout, MountLayout, finishedWork);
      break;
    }
    // ...
  }
}

第四次执行 commitHookEffectList

  • commitRoot 执行完三次循环后,如果 rootWithPendingPassiveEffects 不为空则将 commitPassiveEffects 加入到 scheduleWork 的调度队列中
  • useEffect 执行是个异步过程,要等到所有节点更新完成、浏览器重新渲染 dom 后才会执行 commitPassiveEffects。即 useLayoutEffect 中可以同步的获取更新后的 dom.
// commitRoot 

//... 

if (
    enableHooks &&
    firstEffect !== null &&
    rootWithPendingPassiveEffects !== null
  ) {
    // This commit included a passive effect. These do not need to fire until
    // after the next paint. Schedule an callback to fire them in an async
    // event. To ensure serial execution, the callback will be flushed early if
    // we enter rootWithPendingPassiveEffects commit phase before then.
    let callback = commitPassiveEffects.bind(null, root, firstEffect);
    if (enableSchedulerTracing) {
      // TODO: Avoid this extra callback by mutating the tracing ref directly,
      // like we do at the beginning of commitRoot. I've opted not to do that
      // here because that code is still in flux.
      callback = Schedule_tracing_wrap(callback);
    }
    passiveEffectCallbackHandle = Schedule_scheduleCallback(callback);
    passiveEffectCallback = callback;
  }

//...

commitPassiveEffects

  • 遍历所有 effect 找到标记了 Passive 的执行 commitPassiveHookEffects
function commitPassiveEffects(root: FiberRoot, firstEffect: Fiber): void {
  rootWithPendingPassiveEffects = null;
  passiveEffectCallbackHandle = null;
  passiveEffectCallback = null;

  // Set this to true to prevent re-entrancy
  const previousIsRendering = isRendering;
  isRendering = true;

  let effect = firstEffect;
  do {
    if (effect.effectTag & Passive) {
      let didError = false;
      let error;
      if (__DEV__) {
        invokeGuardedCallback(null, commitPassiveHookEffects, null, effect);
        if (hasCaughtError()) {
          didError = true;
          error = clearCaughtError();
        }
      } else {
        try {
          commitPassiveHookEffects(effect);
        } catch (e) {
          didError = true;
          error = e;
        }
      }
      if (didError) {
        captureCommitPhaseError(effect, error);
      }
    }
    effect = effect.nextEffect;
  } while (effect !== null);

  isRendering = previousIsRendering;

  // Check if work was scheduled by one of the effects
  const rootExpirationTime = root.expirationTime;
  if (rootExpirationTime !== NoWork) {
    requestWork(root, rootExpirationTime);
  }
}

commitPassiveHookEffects

  • 执行两次
  • 第一次 unmountTag -> UnmountPassive, mountTag -> NoHookEffect 因此只执行 useEffectdestroy
  • 第二次unmountTag -> NoHookEffect, 只执行 useEffectcreate
function commitPassiveHookEffects(finishedWork: Fiber): void {
  commitHookEffectList(UnmountPassive, NoHookEffect, finishedWork);
  commitHookEffectList(NoHookEffect, MountPassive, finishedWork);
}

useContext

  • 调用 readContext 获取 context
function useContext<T>(
  context: ReactContext<T>,
  observedBits: void | number | boolean,
): T {
  // Ensure we're in a function component (class components support only the
  // .unstable_read() form)
  resolveCurrentlyRenderingFiber();
  return readContext(context, observedBits);
}

useRef

  • ref 对象挂载到 hook.memoizedState 属性上。
function useRef<T>(initialValue: T): {current: T} {
  currentlyRenderingFiber = resolveCurrentlyRenderingFiber();
  workInProgressHook = createWorkInProgressHook();
  let ref;

  if (workInProgressHook.memoizedState === null) {
    // ref 对象
    ref = {current: initialValue};
    if (__DEV__) {
      Object.seal(ref);
    }
    workInProgressHook.memoizedState = ref;
  } else {
    ref = workInProgressHook.memoizedState;
  }
  return ref;
}

useImperativeHandle

  • 作用: 子组件配合 forwardRef 往父组件传递方法或者属性
  • 内部使用 useLayoutEffect
function useImperativeMethods<T>(
  ref: {current: T | null} | ((inst: T | null) => mixed) | null | void,
  create: () => T,
  inputs: Array<mixed> | void | null,
): void {
  // TODO: If inputs are provided, should we skip comparing the ref itself?
  const nextInputs =
    inputs !== null && inputs !== undefined
      ? inputs.concat([ref])
      : [ref, create];

  // TODO: I've implemented this on top of useEffect because it's almost the
  // same thing, and it would require an equal amount of code. It doesn't seem
  // like a common enough use case to justify the additional size.
  useLayoutEffect(() => {
    // 如果传递下来的 ref 是个函数
    if (typeof ref === 'function') {
      const refCallback = ref;
      const inst = create();
      // 将 create 方法执行结果当作参数
      refCallback(inst);
      return () => refCallback(null);
    } else if (ref !== null && ref !== undefined) {
      const refObject = ref;
      const inst = create();
      // 直接将 create 方法的结果挂载到传递下来的 ref.current 上
      refObject.current = inst;
      return () => {
        refObject.current = null;
      };
    }
  }, nextInputs);
}

useCallback

  • 如果依赖项相同则返回同一个 callback
function useCallback<T>(
  callback: T,
  inputs: Array<mixed> | void | null,
): T {
  currentlyRenderingFiber = resolveCurrentlyRenderingFiber();
  workInProgressHook = createWorkInProgressHook();

  const nextInputs =
    inputs !== undefined && inputs !== null ? inputs : [callback];

  const prevState = workInProgressHook.memoizedState;
  if (prevState !== null) {
    const prevInputs = prevState[1];
    if (areHookInputsEqual(nextInputs, prevInputs)) {
      return prevState[0];
    }
  }
  workInProgressHook.memoizedState = [callback, nextInputs];
  return callback;
}

useMemo

  • 返回的是 nextCreate 的执行结果,如果依赖项相同,则会返回上一次的执行结果
function useMemo<T>(
  nextCreate: () => T,
  inputs: Array<mixed> | void | null,
): T {
  currentlyRenderingFiber = resolveCurrentlyRenderingFiber();
  workInProgressHook = createWorkInProgressHook();

  const nextInputs =
    inputs !== undefined && inputs !== null ? inputs : [nextCreate];

  const prevState = workInProgressHook.memoizedState;
  if (prevState !== null) {
    const prevInputs = prevState[1];
    if (areHookInputsEqual(nextInputs, prevInputs)) {
      return prevState[0];
    }
  }

  const nextValue = nextCreate();
  workInProgressHook.memoizedState = [nextValue, nextInputs];
  return nextValue;
}

commitRoot

commitRoot

  • renderRoot 最后会调用 onComplete 方法设置 root.finishedWork(即 RootFiber ) 和 root.pendingCommitExpirationTime

    // renderRoot
    // finishedWork 即 RootFiber 对象
    const rootWorkInProgress = root.current.alternate;
    // Ready to commit.
    onComplete(root, rootWorkInProgress, expirationTime);
    
    // onComplete
    function onComplete(
      root: FiberRoot,
      finishedWork: Fiber,
      expirationTime: ExpirationTime,
    ) {
      root.pendingCommitExpirationTime = expirationTime;
      root.finishedWork = finishedWork;
    }
  • performWorkOnRoot 如果 finishedWork 不为 null,则调用 completeRoot -> commitRoot

    function performWorkOnRoot(
      root: FiberRoot,
      expirationTime: ExpirationTime,
      isExpired: boolean,
    ) {
      // ....
      renderRoot(root, isYieldy, isExpired);
      finishedWork = root.finishedWork;
      if (finishedWork !== null) {
        // We've completed the root. Commit it.
        completeRoot(root, finishedWork, expirationTime);
      }
      // ...
    }
    
    // completeRoot 
    function completeRoot(
      root: FiberRoot,
      finishedWork: Fiber,
      expirationTime: ExpirationTime,
    ): void {
      // ...
      commitRoot(root, finishedWork);
    }
  • commit 阶段不能被中断,尽量少的任务交给 commit 阶段

  • 三次循环,都是对 firstEffectlastEffect 单向链上根据不同 effectTag 进行不同的更新

    function commitRoot(root: FiberRoot, finishedWork: Fiber): void {
      // renderRoot 中也是 true, 表示正在进行更新操作
      isWorking = true;
      // 标记提交阶段
      isCommitting = true;
      startCommitTimer();
    
      // 保存当前正在更新的 expirationTime
      const committedExpirationTime = root.pendingCommitExpirationTime;
    
      root.pendingCommitExpirationTime = NoWork;
    
      // Update the pending priority levels to account for the work that we are
      // about to commit. This needs to happen before calling the lifecycles, since
      // they may schedule additional updates.
      // 自己的 expirationTime, 
      const updateExpirationTimeBeforeCommit = finishedWork.expirationTime;
      // 所有子树中优先级最高的任务
      const childExpirationTimeBeforeCommit = finishedWork.childExpirationTime;
      // 找到这两个当中最小且非 NoWork 的 expirationTime
      const earliestRemainingTimeBeforeCommit =
        updateExpirationTimeBeforeCommit === NoWork ||
        (childExpirationTimeBeforeCommit !== NoWork &&
          childExpirationTimeBeforeCommit < updateExpirationTimeBeforeCommit)
          ? childExpirationTimeBeforeCommit
          : updateExpirationTimeBeforeCommit;
          // 标记优先级
      markCommittedPriorityLevels(root, earliestRemainingTimeBeforeCommit);
    
      // Reset this to null before calling lifecycles
      ReactCurrentOwner.current = null;
    
      let firstEffect;
      // 
      if (finishedWork.effectTag > PerformedWork) {
        // A fiber's effect list consists only of its children, not itself. So if
        // the root has an effect, we need to add it to the end of the list. The
        // resulting list is the set that would belong to the root's parent, if
        // it had one; that is, all the effects in the tree including the root.
        if (finishedWork.lastEffect !== null) {
          // 自身的如果有 effect 且 lastEffect 不为 null,需要加入到单链表的最后 
          finishedWork.lastEffect.nextEffect = finishedWork;
          firstEffect = finishedWork.firstEffect;
        } else {
          firstEffect = finishedWork;
        }
      } else {
        // There is no effect on the root.
        // 没有 effect 就用第一个effect
        firstEffect = finishedWork.firstEffect;
      }
      // ...三次循环
      prepareForCommit(root.containerInfo);
      // Invoke instances of getSnapshotBeforeUpdate before mutation.
      // 每次循环完都需要重新赋值全局变量 nextEffect = firstEffect ,即从头到尾开始走三次循环
      nextEffect = firstEffect;
      startCommitSnapshotEffectsTimer();
      // 第一次循环, 调用 classComponent 可能存在的 getSnapshotBeforeUpdate 生命周期方法
      while (nextEffect !== null) {
        let didError = false;
        let error;
        if (__DEV__) {
          invokeGuardedCallback(null, commitBeforeMutationLifecycles, null);
          if (hasCaughtError()) {
            didError = true;
            error = clearCaughtError();
          }
        } else {
          try {
            commitBeforeMutationLifecycles();
          } catch (e) {
            didError = true;
            error = e;
          }
        }
        if (didError) {
          invariant(
            nextEffect !== null,
            'Should have next effect. This error is likely caused by a bug ' +
              'in React. Please file an issue.',
          );
          captureCommitPhaseError(nextEffect, error);
          // Clean-up
          if (nextEffect !== null) {
            nextEffect = nextEffect.nextEffect;
          }
        }
      }
      stopCommitSnapshotEffectsTimer();
    
      if (enableProfilerTimer) {
        // Mark the current commit time to be shared by all Profilers in this batch.
        // This enables them to be grouped later.
        recordCommitTime();
      }
    
      // Commit all the side-effects within a tree. We'll do this in two passes.
      // The first pass performs all the host insertions, updates, deletions and
      // ref unmounts.
      nextEffect = firstEffect;
      startCommitHostEffectsTimer();
      // 第二次循环,操作 dom 节点的插入,删除,更新操作
      while (nextEffect !== null) {
        let didError = false;
        let error;
        if (__DEV__) {
          invokeGuardedCallback(null, commitAllHostEffects, null);
          if (hasCaughtError()) {
            didError = true;
            error = clearCaughtError();
          }
        } else {
          try {
            // 操作 dom 节点的插入、删除、更新操作
            commitAllHostEffects();
          } catch (e) {
            didError = true;
            error = e;
          }
        }
        if (didError) {
          invariant(
            nextEffect !== null,
            'Should have next effect. This error is likely caused by a bug ' +
              'in React. Please file an issue.',
          );
          captureCommitPhaseError(nextEffect, error);
          // Clean-up
          if (nextEffect !== null) {
            nextEffect = nextEffect.nextEffect;
          }
        }
      }
      stopCommitHostEffectsTimer();
    
      resetAfterCommit(root.containerInfo);
    
      // The work-in-progress tree is now the current tree. This must come after
      // the first pass of the commit phase, so that the previous tree is still
      // current during componentWillUnmount, but before the second pass, so that
      // the finished work is current during componentDidMount/Update.
      root.current = finishedWork;
    
      // In the second pass we'll perform all life-cycles and ref callbacks.
      // Life-cycles happen as a separate pass so that all placements, updates,
      // and deletions in the entire tree have already been invoked.
      // This pass also triggers any renderer-specific initial effects.
      nextEffect = firstEffect;
      startCommitLifeCyclesTimer();
      // 第三次循环,组件的生命周期方法调用
      while (nextEffect !== null) {
        let didError = false;
        let error;
        if (__DEV__) {
          invokeGuardedCallback(
            null,
            commitAllLifeCycles,
            null,
            root,
            committedExpirationTime,
          );
          if (hasCaughtError()) {
            didError = true;
            error = clearCaughtError();
          }
        } else {
          try {
            // 组件生命周期调用
            commitAllLifeCycles(root, committedExpirationTime);
          } catch (e) {
            didError = true;
            error = e;
          }
        }
        if (didError) {
          invariant(
            nextEffect !== null,
            'Should have next effect. This error is likely caused by a bug ' +
              'in React. Please file an issue.',
          );
          captureCommitPhaseError(nextEffect, error);
          if (nextEffect !== null) {
            nextEffect = nextEffect.nextEffect;
          }
        }
      }
      // 全局遍量 reset 
      isCommitting = false;
      isWorking = false;
      stopCommitLifeCyclesTimer();
      stopCommitTimer();
      onCommitRoot(finishedWork.stateNode);
      // 在执行 classComponent 的生命周期方法时又可能产生新的更新,childExpirationTime 可能会改变
      const updateExpirationTimeAfterCommit = finishedWork.expirationTime;
      const childExpirationTimeAfterCommit = finishedWork.childExpirationTime;
      const earliestRemainingTimeAfterCommit =
        updateExpirationTimeAfterCommit === NoWork ||
        (childExpirationTimeAfterCommit !== NoWork &&
          childExpirationTimeAfterCommit < updateExpirationTimeAfterCommit)
          ? childExpirationTimeAfterCommit
          : updateExpirationTimeAfterCommit;
      if (earliestRemainingTimeAfterCommit === NoWork) {
        // If there's no remaining work, we can clear the set of already failed
        // error boundaries.
        legacyErrorBoundariesThatAlreadyFailed = null;
      }
      // 设置 root.expirationTime 为新计算出来的上面两个值中 小的并且非 NoWork 的 expirationTime
      onCommit(root, earliestRemainingTimeAfterCommit);
    }

第一次循环 commitBeforeMutationLifecycles

  • 调用 classComponent 可能存在的 getSnapshotBeforeUpdate 生命周期方法
  • 只有 classComponent 才会调用 getSnapshotBeforeUpdate
  • 将生成的快照挂载在 instance.__reactInternalSnapshotBeforeUpdate 上,在 componentDidUpdate 里会接受这个参数
// ReactFiberScheduler.js 中的 commitBeforeMutationLifecycles 方法
function commitBeforeMutationLifecycles() {
  while (nextEffect !== null) {
    if (__DEV__) {
      ReactCurrentFiber.setCurrentFiber(nextEffect);
    }

    const effectTag = nextEffect.effectTag;
    // 如果有 snapshot effect,在 beginWork 中更新 classComponent -> updateInstance 中如果组件实例有 getSnapshotBeforeUpdate 则会标记 snapshot
    if (effectTag & Snapshot) {
      recordEffect();
      const current = nextEffect.alternate; // workInProgress 
      // 这里调用的是 ReactFiberCommitWork.js 中的同名方法
      commitBeforeMutationLifeCycles(current, nextEffect);
    }

    // Don't cleanup effects yet;
    // This will be done by commitAllLifeCycles()
    nextEffect = nextEffect.nextEffect;
  }
}
// 只有 classComponent 才会调用 getSnapshotBeforeUpdate
function commitBeforeMutationLifeCycles(
  current: Fiber | null,
  finishedWork: Fiber,
): void {
  switch (finishedWork.tag) {
    case ClassComponent: {
      if (finishedWork.effectTag & Snapshot) {
        // 非首次渲染
        if (current !== null) {
          // 老 props 
          const prevProps = current.memoizedProps;
          // 老 state 
          const prevState = current.memoizedState;
          startPhaseTimer(finishedWork, 'getSnapshotBeforeUpdate');
          // 组件实例
          const instance = finishedWork.stateNode;
          instance.props = finishedWork.memoizedProps;
          instance.state = finishedWork.memoizedState;
          // 获取快照, getSnapshotBeforeUpdate 方法里可以调用 this 
          const snapshot = instance.getSnapshotBeforeUpdate(
            prevProps,
            prevState,
          );
          if (__DEV__) {}
          // 获取的快照挂载在 instance 上,componentDidUpdate 方法的第三个参数即 snapshot
          instance.__reactInternalSnapshotBeforeUpdate = snapshot;
          stopPhaseTimer();
        }
      }
      return;
    }
    case HostRoot:
    case HostComponent:
    case HostText:
    case HostPortal:
    case IncompleteClassComponent:
      // Nothing to do for these component types
      return;
    default: {}
  }
}

第二次循环 commitAllHostEffects

  • 操作 dom 节点的插入、删除、更新操作
  • 操作 ref
function commitAllHostEffects() {
  while (nextEffect !== null) {

    recordEffect();

    const effectTag = nextEffect.effectTag;

    // 判断内部是否为文字节点
    if (effectTag & ContentReset) {
      // 文字节点设置为空字符串
      commitResetTextContent(nextEffect);
    }

    // ref操作
    if (effectTag & Ref) {
      const current = nextEffect.alternate;
      if (current !== null) {
        commitDetachRef(current);
      }
    }

    // The following switch statement is only concerned about placement,
    // updates, and deletions. To avoid needing to add a case for every
    // possible bitmap value, we remove the secondary effects from the
    // effect tag and switch on that value.
    // 插入、更新、删除的 effect 
    let primaryEffectTag = effectTag & (Placement | Update | Deletion);
    switch (primaryEffectTag) {
      // 新增的,插入
      case Placement: {
        
        commitPlacement(nextEffect);
        // Clear the "placement" from effect tag so that we know that this is inserted, before
        // any life-cycles like componentDidMount gets called.
        // TODO: findDOMNode doesn't rely on this any more but isMounted
        // does and isMounted is deprecated anyway so we should be able
        // to kill this.
        // 执行插入操作后,要去掉 effectTag
        nextEffect.effectTag &= ~Placement;
        break;
      }
      // 插入且更新
      case PlacementAndUpdate: {
        // Placement
        commitPlacement(nextEffect);
        // Clear the "placement" from effect tag so that we know that this is inserted, before
        // any life-cycles like componentDidMount gets called.
        // 执行插入操作后,要去掉 effectTag
        nextEffect.effectTag &= ~Placement;

        // Update
        // 节点之前存在,有新的 content 或者 attribute 要更新 ,执行 commitWork
        // 只会更新 HostComponent, HostText
        // nextEffect 为新的
        // current 为老的
        const current = nextEffect.alternate;
        commitWork(current, nextEffect);
        break;
      }
      // 更新
      case Update: {
        const current = nextEffect.alternate;
        commitWork(current, nextEffect);
        break;
      }
      case Deletion: {
        commitDeletion(nextEffect);
        break;
      }
    }
    nextEffect = nextEffect.nextEffect;
  }

}

commitResetTextContent 文字节点设置为空字符串

function commitResetTextContent(current: Fiber) {
  if (!supportsMutation) {
    return;
  }
  resetTextContent(current.stateNode);
}
// resetTextContent
function resetTextContent(domElement: Instance): void {
  setTextContent(domElement, '');
}

// setTextContent 设置文本内容
setTextContent = function(node: Element, text: string): void {
  if (text) {
    let firstChild = node.firstChild;

    if (
      firstChild &&
      firstChild === node.lastChild &&
      firstChild.nodeType === TEXT_NODE
    ) {
      firstChild.nodeValue = text;
      return;
    }
  }
  node.textContent = text;
};

commitDetachRef 清空ref

function commitDetachRef(current: Fiber) {
  const currentRef = current.ref;
  if (currentRef !== null) {
    if (typeof currentRef === 'function') {
      currentRef(null);
    } else {
      currentRef.current = null;
    }
  }
}

Placement 插入:commitPlacement

  • 调用 insertBefore 方法需要找父节点,getHostParentFiber 即往上找到第一个节点类型是 原生 dom 节点Root 节点portal 的父节点
  • getHostSibling 方法用来找插入的节点的参考节点 parent.insertBefore(newNode, beforeNode), 如果有 beforeNode, 则调用 insert 插入,否则是 appendChild
function commitPlacement(finishedWork: Fiber): void {
  // ReactDOM 调用 supportsMutation 默认为 true 
  if (!supportsMutation) {
    return;
  }

  // Recursively insert all host nodes into the parent.
  // 往上找到第一个节点类型是 原生dom节点、Root 节点、portal的父节点
  const parentFiber = getHostParentFiber(finishedWork);

  // Note: these two variables *must* always be updated together.
  let parent;
  // 是否为容器型
  let isContainer;

  switch (parentFiber.tag) {
    case HostComponent:
      parent = parentFiber.stateNode;
      isContainer = false;
      break;
    case HostRoot:
      parent = parentFiber.stateNode.containerInfo;
      isContainer = true;
      break;
    case HostPortal:
      parent = parentFiber.stateNode.containerInfo;
      isContainer = true;
      break;
    default:
      invariant(
        false,
        'Invalid host parent fiber. This error is likely caused by a bug ' +
          'in React. Please file an issue.',
      );
  }
  if (parentFiber.effectTag & ContentReset) {
    // Reset the text content of the parent before doing any insertions
    // 清除父节点的文本节点
    resetTextContent(parent);
    // Clear ContentReset from the effect tag
    parentFiber.effectTag &= ~ContentReset;
  }

  // 找需要使用 insertBefore 来插入的节点 , parent.insertBefore(newNode, beforeNode)
  const before = getHostSibling(finishedWork);
  // We only have the top Fiber that was inserted but we need recurse down its
  // children to find all the terminal nodes.
  let node: Fiber = finishedWork;
  while (true) {
    // 原生 dom 节点 或者 文本节点
    if (node.tag === HostComponent || node.tag === HostText) {
      // insertBefore 插入
      if (before) {
        if (isContainer) {
          insertInContainerBefore(parent, node.stateNode, before);
        } else {
          insertBefore(parent, node.stateNode, before);
        }
      } else {
        // appendChild 
        if (isContainer) {
          appendChildToContainer(parent, node.stateNode);
        } else {
          appendChild(parent, node.stateNode);
        }
      }
    } else if (node.tag === HostPortal) {
      // If the insertion itself is a portal, then we don't want to traverse
      // down its children. Instead, we'll get insertions from each child in
      // the portal directly.
    } else if (node.child !== null) {
      // 这里和 completeWork 里的 appendAllChildren 类似
      // 即节点不是 dom 节点(文本节点), 并且 node.child 不为 null,应该要遍历其子节点,其实就是 classComponent 
      node.child.return = node;
      node = node.child;
      continue;
    }
    if (node === finishedWork) {
      return;
    }
    // 深度优先遍历
    // 这个小循环找有兄弟节点的父节点,如果 sibling 不为 null 则会去遍历兄弟节点
    // 如果没有兄弟节点,继续往上找,直到没有父节点 或者 父节点为当前遍历的节点时停止循环
    while (node.sibling === null) {
      if (node.return === null || node.return === finishedWork) {
        return;
      }
      node = node.return;
    }
    node.sibling.return = node.return;
    // 继续遍历兄弟节点
    node = node.sibling;
  }
}

getHostParentFiber

  • 往上找第一个节点类型是 原生 dom 节点Root 节点portal 的父节点
function getHostParentFiber(fiber: Fiber): Fiber {
  let parent = fiber.return;
  while (parent !== null) {
    if (isHostParent(parent)) {
      return parent;
    }
    parent = parent.return;
  }
}

function isHostParent(fiber: Fiber): boolean {
  return (
    fiber.tag === HostComponent ||
    fiber.tag === HostRoot ||
    fiber.tag === HostPortal
  );
}

getHostSibling

  • 找插入节点的参考节点: 即第一个类型是 HostComponent 或者 HostText 的兄弟节点
  • 如果没有兄弟节点,则找往上找第一个是 dom 节点 的父节点,或者从父节点里的兄弟节点里找第一个 dom 节点
  • 参考节点的 effectTag 不是 placement 才返回
function getHostSibling(fiber: Fiber): ?Instance {
  // We're going to search forward into the tree until we find a sibling host
  // node. Unfortunately, if multiple insertions are done in a row we have to
  // search past them. This leads to exponential search for the next sibling.
  // TODO: Find a more efficient way to do this.
  // fiber 是待插入(insertBefore 或者 appendChild )的那个节点,如果要使用 insertBefore 就要找到它的第一个原生 dom 节点的兄弟节点
  let node: Fiber = fiber;
  siblings: while (true) {
    // If we didn't find anything, let's try the next sibling.
    // 如果没有兄弟节点,则一直往上找父节点,直到找到父节点是 HostComponent、HostRoot、HostPortal这三种或者为null为止,即确定父节点肯定是原生 dom 节点
    // 或者从父节点的兄弟节点里找 dom 节点
    while (node.sibling === null) {
      if (node.return === null || isHostParent(node.return)) {
        // If we pop out of the root or hit the parent the fiber we are the
        // last sibling.
        return null;
      }
      node = node.return;
    }
    // 有兄弟节点的情况
    node.sibling.return = node.return;
    // 从兄弟节点开始找第一个原生 dom 节点
    node = node.sibling;
    // 如果不是原生 dom 节点 或者 text,那么就要去找它(classComponent)的 child 
    while (node.tag !== HostComponent && node.tag !== HostText) {
      // If it is not host node and, we might have a host node inside it.
      // Try to search down until we find one.
      // 如果这个节点也是标记插入,那么要跳过
      if (node.effectTag & Placement) {
        // If we don't have a child, try the siblings instead.
        continue siblings;
      }
      // If we don't have a child, try the siblings instead.
      // We also skip portals because they are not part of this host tree.
      // 没有 child 或者是 portal 跳过
      if (node.child === null || node.tag === HostPortal) {
        continue siblings;
      } else {
        // 从 child 里找第一个 dom 节点
        node.child.return = node;
        node = node.child;
      }
    }
    // Check if this host node is stable or about to be placed.
    // 到这 node 肯定是原生 dom 节点了,并且是要没有标记插入的才算真正找到
    if (!(node.effectTag & Placement)) {
      // Found it!
      return node.stateNode;
    }
  }
}

insertBefore or appendChild

function insertBefore(
  parentInstance: Instance,
  child: Instance | TextInstance,
  beforeChild: Instance | TextInstance,
): void {
  parentInstance.insertBefore(child, beforeChild);
}

function appendChild(
  parentInstance: Instance,
  child: Instance | TextInstance,
): void {
  parentInstance.appendChild(child);
}

PlacementAndUpdate 插入和更新:commitPlacement、commitWork

Updates 更新:commitWork

  • 执行完 commitPlacement 后要去掉 placementeffectTag
  • 节点之前存在,有新的 content 或者 attribute 要更新时,执行 commitWork
  • 只会更新 HostComponent,HostText
// current 是老的节点
// newEffect 是新的节点
const current = nextEffect.alternate;
commitWork(current, nextEffect);

function commitWork(current: Fiber | null, finishedWork: Fiber): void {
  if (!supportsMutation) {
    commitContainer(finishedWork);
    return;
  }

  switch (finishedWork.tag) {
    case ClassComponent: {
      return;
    }
    case HostComponent: {
      const instance: Instance = finishedWork.stateNode;
      // 节点已经创建
      if (instance != null) {
        // Commit the work prepared earlier.
        // 新节点上的 props
        const newProps = finishedWork.memoizedProps;
        // For hydration we reuse the update path but we treat the oldProps
        // as the newProps. The updatePayload will contain the real change in
        // this case.
        // 老节点上的 props
        const oldProps = current !== null ? current.memoizedProps : newProps;
        // dom 标签
        const type = finishedWork.type;
        // TODO: Type the updateQueue to be specific to host components.
        // updatePayload 为 diffProperties 是创建的 [k1, v1, k2, v2] 格式的需要更新的 prop 的数组
        const updatePayload: null | UpdatePayload = (finishedWork.updateQueue: any);
        finishedWork.updateQueue = null;
        if (updatePayload !== null) {
          commitUpdate(
            instance,
            updatePayload,
            type,
            oldProps,
            newProps,
            finishedWork,
          );
        }
      }
      return;
    }
    case HostText: {
      const textInstance: TextInstance = finishedWork.stateNode;
      const newText: string = finishedWork.memoizedProps;
      // For hydration we reuse the update path but we treat the oldProps
      // as the newProps. The updatePayload will contain the real change in
      // this case.
      const oldText: string =
        current !== null ? current.memoizedProps : newText;
        // 文本节点直接设置 textInstance.nodeValue = newText;
      commitTextUpdate(textInstance, oldText, newText);
      return;
    }
    case HostRoot: {
      return;
    }
    case Profiler: {
      return;
    }
    case SuspenseComponent: {
      return;
    }
    case IncompleteClassComponent: {
      return;
    }
    default: {
    }
  }
}

commitUpdate

  • 更新 dom 上挂载的 props
  • 更新 property
function commitUpdate(
  domElement: Instance,
  updatePayload: Array<mixed>,
  type: string,
  oldProps: Props,
  newProps: Props,
  internalInstanceHandle: Object,
): void {
  // Update the props handle so that we know which props are the ones with
  // with current event handlers.
  updateFiberProps(domElement, newProps);
  // Apply the diff to the DOM node.
  updateProperties(domElement, updatePayload, type, oldProps, newProps);
}

updateProperties

  • updatePayload 上需要更新的属性更新
  • input radio 标签单独处理,更新 checked
function updateProperties(
  domElement: Element,
  updatePayload: Array<any>,
  tag: string,
  lastRawProps: Object,
  nextRawProps: Object,
): void {
  // Update checked *before* name.
  // In the middle of an update, it is possible to have multiple checked.
  // When a checked radio tries to change name, browser makes another radio's checked false.
  if (
    tag === 'input' &&
    nextRawProps.type === 'radio' &&
    nextRawProps.name != null
  ) {
    ReactDOMInput.updateChecked(domElement, nextRawProps);
  }

  const wasCustomComponentTag = isCustomComponent(tag, lastRawProps);
  const isCustomComponentTag = isCustomComponent(tag, nextRawProps);
  // Apply the diff.
  // 更新 property 
  updateDOMProperties(
    domElement,
    updatePayload,
    wasCustomComponentTag,
    isCustomComponentTag,
  );

  // TODO: Ensure that an update gets scheduled if any of the special props
  // changed.
  // 对可交互的标签单独处理
  switch (tag) {
    case 'input':
      // Update the wrapper around inputs *after* updating props. This has to
      // happen after `updateDOMProperties`. Otherwise HTML5 input validations
      // raise warnings and prevent the new value from being assigned.
      ReactDOMInput.updateWrapper(domElement, nextRawProps);
      break;
    case 'textarea':
      ReactDOMTextarea.updateWrapper(domElement, nextRawProps);
      break;
    case 'select':
      // <select> value update needs to occur after <option> children
      // reconciliation
      ReactDOMSelect.postUpdateWrapper(domElement, nextRawProps);
      break;
  }
}

updateDOMProperties

  • updatePayload [k1, v1, k2, v2] 遍历更新
// 和 completeWork 中 finalizeInitialChildren 初始化属性做的事相似
function updateDOMProperties(
  domElement: Element,
  updatePayload: Array<any>,
  wasCustomComponentTag: boolean,
  isCustomComponentTag: boolean,
): void {
  // TODO: Handle wasCustomComponentTag
  for (let i = 0; i < updatePayload.length; i += 2) {
    const propKey = updatePayload[i];
    const propValue = updatePayload[i + 1];
    if (propKey === STYLE) {
      // 设置 style 属性,
      CSSPropertyOperations.setValueForStyles(domElement, propValue);
    } else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
      // 设置 html
      setInnerHTML(domElement, propValue);
    } else if (propKey === CHILDREN) {
      // 设置 textContent
      setTextContent(domElement, propValue);
    } else {
      // 设置 dom 属性
      DOMPropertyOperations.setValueForProperty(
        domElement,
        propKey,
        propValue,
        isCustomComponentTag,
      );
    }
  }
}

Deletion 删除:commitDeletion

  • 遍历子树
  • 卸载 ref
  • classComponent 执行 componentWillUnmount 方法
  • 清空 fiber 上的相关属性
function commitDeletion(current: Fiber): void {
  if (supportsMutation) {
    // Recursively delete all host nodes from the parent.
    // Detach refs and call componentWillUnmount() on the whole subtree.
    // ReactDOM 只会走这个方法
    unmountHostComponents(current);
  } else {
    // Detach refs and call componentWillUnmount() on the whole subtree.
    commitNestedUnmounts(current);
  }
  // 清空 Fiber 上的属性
  detachFiber(current);
}

unmountHostComponents

function unmountHostComponents(current): void {
  // We only have the top Fiber that was deleted but we need recurse down its
  // children to find all the terminal nodes.
  let node: Fiber = current;

  // Each iteration, currentParent is populated with node's host parent if not
  // currentParentIsValid.
  let currentParentIsValid = false;

  // Note: these two variables *must* always be updated together.
  let currentParent;
  let currentParentIsContainer;

  while (true) {
    if (!currentParentIsValid) {
      // 找到 parent 是 HostComponent、HostRoot、HostPortal 类型的节点赋值给 currentParent
      let parent = node.return;
      findParent: while (true) {
        switch (parent.tag) {
          case HostComponent:
            currentParent = parent.stateNode;
            currentParentIsContainer = false;
            break findParent;
          case HostRoot:
            currentParent = parent.stateNode.containerInfo;
            currentParentIsContainer = true;
            break findParent;
          case HostPortal:
            currentParent = parent.stateNode.containerInfo;
            currentParentIsContainer = true;
            break findParent;
        }
        parent = parent.return;
      }
      currentParentIsValid = true;
    }

    if (node.tag === HostComponent || node.tag === HostText) {
      // 原生 dom 节点的子节点下 也有 classComponent 或者 portal,因此要递归调用遍历整个树的每个节点
      commitNestedUnmounts(node);
      // After all the children have unmounted, it is now safe to remove the
      // node from the tree.
      // 如果是原生 dom 节点,直接 removeChild 删除
      if (currentParentIsContainer) {
        removeChildFromContainer((currentParent: any), node.stateNode);
      } else {
        removeChild((currentParent: any), node.stateNode);
      }
      // Don't visit children because we already visited them.
    } else if (node.tag === HostPortal) {
      // When we go into a portal, it becomes the parent to remove from.
      // We will reassign it back when we pop the portal on the way up.
      // 如果是 HostPortal 继续循环子节点
      currentParent = node.stateNode.containerInfo;
      currentParentIsContainer = true;
      // Visit children because portals might contain host components.
      if (node.child !== null) {
        // 子节点不为 null 继续往下遍历
        node.child.return = node;
        node = node.child;
        continue;
      }
    } else {
      // 非 dom 节点 和非 portal ,即 classComponent
      commitUnmount(node);
      // Visit children because we may find more host components below.
      if (node.child !== null) {
        // 子节点不为 null 继续往下遍历
        node.child.return = node;
        node = node.child;
        continue;
      }
    }
    if (node === current) {
      return;
    }
    // 深度遍历,往上找 sibling
    while (node.sibling === null) {
      if (node.return === null || node.return === current) {
        return;
      }
      node = node.return;
      if (node.tag === HostPortal) {
        // When we go out of the portal, we need to restore the parent.
        // Since we don't keep a stack of them, we will search for it.
        currentParentIsValid = false;
      }
    }
    node.sibling.return = node.return;
    node = node.sibling;
  }
}

commitUnmount

  • 卸载 ref
  • 执行 componentWillUnmount
  • 对于 portal 会递归调用 unmountHostComponents
function commitUnmount(current: Fiber): void {
  onCommitUnmount(current);

  switch (current.tag) {
    case ClassComponent: {
      // 卸载 ref 
      safelyDetachRef(current);
      const instance = current.stateNode;
      // 执行 componentWillUnmount
      if (typeof instance.componentWillUnmount === 'function') {
        safelyCallComponentWillUnmount(current, instance);
      }
      return;
    }
    case HostComponent: {
      // 卸载 ref
      safelyDetachRef(current);
      return;
    }
    case HostPortal: {
      // TODO: this is recursive.
      // We are also not using this parent because
      // the portal will get pushed immediately.
      if (supportsMutation) {
        // 递归调用 unmountHostComponents
        unmountHostComponents(current);
      } else if (supportsPersistence) {
        emptyPortalContainer(current);
      }
      return;
    }
  }
}

// safelyDetachRef 
function safelyDetachRef(current: Fiber) {
  const ref = current.ref;
  if (ref !== null) {
    if (typeof ref === 'function') {
      if (__DEV__) 
      } else {
        try {
          ref(null);
        } catch (refError) {
          captureCommitPhaseError(current, refError);
        }
      }
    } else {
      ref.current = null;
    }
  }
}

commitNestedUnmounts

  • HostComponent 执行卸载,原生 dom 节点 的子节点下也有 classComponent 或者 portal
  • 嵌套递归卸载子树达到卸载 HostPortal 的目的
function commitNestedUnmounts(root: Fiber): void {
  // While we're inside a removed host node we don't want to call
  // removeChild on the inner nodes because they're removed by the top
  // call anyway. We also want to call componentWillUnmount on all
  // composites before this host node is removed from the tree. Therefore
  // we do an inner loop while we're still inside the host node.
  let node: Fiber = root;
  while (true) {
    // 如果 node 是 portal (只会发生在 portal 为 原生 dom 节点的 sibling 的情况,如果要卸载的组件的第一个 child 就是 portal,那么就会在 unmountHostComponents 里循环卸载子节点了),-> commitUnmount -> unmountHostComponents -> portal 会去遍历 child -> child 为 hostComponent -> commitNestedUnmounts -> commitUnmount 卸载 child 
    // commitUnmount 卸载 child  -> portal 会执行 removeChildFromContainer -> 最后依次返回
    // 嵌套递归调用卸载节点,达到卸载 HostPortal 的目的
    commitUnmount(node);
    // Visit children because they may contain more composite or host nodes.
    // Skip portals because commitUnmount() currently visits them recursively.
    if (
      node.child !== null &&
      // If we use mutation we drill down into portals using commitUnmount above.
      // If we don't use mutation we drill down into portals here instead.
      (!supportsMutation || node.tag !== HostPortal)
    ) {
      // 不是 portal 并且有 child,继续循环
      node.child.return = node;
      node = node.child;
      continue;
    }
    if (node === root) {
      return;
    }
    // 深度遍历,往上找 sibling
    while (node.sibling === null) {
      if (node.return === null || node.return === root) {
        return;
      }
      node = node.return;
    }
    node.sibling.return = node.return;
    node = node.sibling;
  }
}

removeChildFromContainer & removeChild

function removeChild(
  parentInstance: Instance,
  child: Instance | TextInstance,
): void {
  parentInstance.removeChild(child);
}

function removeChildFromContainer(
  container: Container,
  child: Instance | TextInstance,
): void {
  if (container.nodeType === COMMENT_NODE) {
    (container.parentNode: any).removeChild(child);
  } else {
    container.removeChild(child);
  }
}

第三次循环 commitAllLifeCycles

  • 组件的生命周期方法调用, 首次渲染执行 componentDidMount, 更新渲染执行 componentDidUpdate
  • 执行 setStatecallback 回调函数, (componentDidCatch 也是在 callback 中执行)

commitAllLifeCycles

  • effectTagUpdate 或者 Callback 则去调用 commitLifeCycles

    function commitAllLifeCycles(
      finishedRoot: FiberRoot,
      committedExpirationTime: ExpirationTime,
    ) {
    
      while (nextEffect !== null) {
        const effectTag = nextEffect.effectTag;
    
        if (effectTag & (Update | Callback)) {
          recordEffect();
          // current 为老的
          const current = nextEffect.alternate;
          commitLifeCycles(
            finishedRoot,
            current,
            nextEffect,
            committedExpirationTime,
          );
        }
    
        if (effectTag & Ref) {
          recordEffect();
          commitAttachRef(nextEffect);
        }
    
        const next = nextEffect.nextEffect;
        // Ensure that we clean these up so that we don't accidentally keep them.
        // I'm not actually sure this matters because we can't reset firstEffect
        // and lastEffect since they're on every node, not just the effectful
        // ones. So we have to clean everything as we reuse nodes anyway.
        nextEffect.nextEffect = null;
        // Ensure that we reset the effectTag here so that we can rely on effect
        // tags to reason about the current life-cycle.
        nextEffect = next;
      }
    }

commitLifeCycles

function commitLifeCycles(
  finishedRoot: FiberRoot,
  current: Fiber | null,
  finishedWork: Fiber,
  committedExpirationTime: ExpirationTime,
): void {
  switch (finishedWork.tag) {
    case ClassComponent: {
      const instance = finishedWork.stateNode;
      if (finishedWork.effectTag & Update) {
        // 首次渲染
        if (current === null) {
          startPhaseTimer(finishedWork, 'componentDidMount');
          instance.props = finishedWork.memoizedProps;
          instance.state = finishedWork.memoizedState;
          instance.componentDidMount();
          stopPhaseTimer();
        } else {
          // 更新渲染
          const prevProps = current.memoizedProps;
          const prevState = current.memoizedState;
          startPhaseTimer(finishedWork, 'componentDidUpdate');
          instance.props = finishedWork.memoizedProps;
          instance.state = finishedWork.memoizedState;
          instance.componentDidUpdate(
            prevProps, // setState 后产生的新的 props
            prevState, // setState 后产生的新的 state 
            instance.__reactInternalSnapshotBeforeUpdate, // Snapshot 快照
          );
          stopPhaseTimer();
        }
      }
      const updateQueue = finishedWork.updateQueue;
      // updateQueue 不为空,要执行 queue 里可能存在的 callback
      if (updateQueue !== null) {
        instance.props = finishedWork.memoizedProps;
        instance.state = finishedWork.memoizedState;
        commitUpdateQueue(
          finishedWork,
          updateQueue,
          instance,
          committedExpirationTime,
        );
      }
      return;
    }
    case HostRoot: {
      // Root 节点, ReactDOM.render 方法会创建一个update,这个方法可以接受的第三个参数就是 callback,在第一次渲染结束后调用
      const updateQueue = finishedWork.updateQueue;
      if (updateQueue !== null) {
        let instance = null;
        if (finishedWork.child !== null) {
          switch (finishedWork.child.tag) {
            case HostComponent:
              instance = getPublicInstance(finishedWork.child.stateNode);
              break;
            case ClassComponent:
              instance = finishedWork.child.stateNode;
              break;
          }
        }
        commitUpdateQueue(
          finishedWork,
          updateQueue,
          instance,
          committedExpirationTime,
        );
      }
      return;
    }
    case HostComponent: {
      const instance: Instance = finishedWork.stateNode;

      // Renderers may schedule work to be done after host components are mounted
      // (eg DOM renderer may schedule auto-focus for inputs and form controls).
      // These effects should only be committed when components are first mounted,
      // aka when there is no current/alternate.
      // 首次渲染且 effecttag 为 update,则会自动 focus 需要 autofocus 的节点
      if (current === null && finishedWork.effectTag & Update) {
        const type = finishedWork.type;
        const props = finishedWork.memoizedProps;
        // Button, Input, Select, Textarea 标签,props.autofocus 为真,则首次渲染会 focus
        commitMount(instance, type, props, finishedWork);
      }

      return;
    }
    case HostText: {
      // We have no life-cycles associated with text.
      return;
    }
    case HostPortal: {
      // We have no life-cycles associated with portals.
      return;
    }
    case Profiler: {
    }
    case SuspenseComponent: {
      if (finishedWork.effectTag & Callback) {
        // In non-strict mode, a suspense boundary times out by commiting
        // twice: first, by committing the children in an inconsistent state,
        // then hiding them and showing the fallback children in a subsequent
        // commit.
        const newState: SuspenseState = {
          alreadyCaptured: true,
          didTimeout: false,
          timedOutAt: NoWork,
        };
        finishedWork.memoizedState = newState;
        scheduleWork(finishedWork, Sync);
        return;
      }
      let oldState: SuspenseState | null =
        current !== null ? current.memoizedState : null;
      let newState: SuspenseState | null = finishedWork.memoizedState;
      let oldDidTimeout = oldState !== null ? oldState.didTimeout : false;

      let newDidTimeout;
      let primaryChildParent = finishedWork;
      if (newState === null) {
        newDidTimeout = false;
      } else {
        newDidTimeout = newState.didTimeout;
        if (newDidTimeout) {
          primaryChildParent = finishedWork.child;
          newState.alreadyCaptured = false;
          if (newState.timedOutAt === NoWork) {
            // If the children had not already timed out, record the time.
            // This is used to compute the elapsed time during subsequent
            // attempts to render the children.
            newState.timedOutAt = requestCurrentTime();
          }
        }
      }

      if (newDidTimeout !== oldDidTimeout && primaryChildParent !== null) {
        hideOrUnhideAllChildren(primaryChildParent, newDidTimeout);
      }
      return;
    }
    case IncompleteClassComponent:
      break;
    default: {}
  }
}

commitUpdateQueue

  • 如果还有有捕获错误的更新,需要清空
  • commitUpdateEffects 调用 setStatte 的回调函数
function commitUpdateQueue<State>(
  finishedWork: Fiber,
  finishedQueue: UpdateQueue<State>,
  instance: any,
  renderExpirationTime: ExpirationTime,
): void {
  // If the finished render included captured updates, and there are still
  // lower priority updates left over, we need to keep the captured updates
  // in the queue so that they are rebased and not dropped once we process the
  // queue again at the lower priority.
  // 如果有捕获错误的更新,即一次渲染过程中还没有完成的更新
  if (finishedQueue.firstCapturedUpdate !== null) {
    // Join the captured update list to the end of the normal list.
    if (finishedQueue.lastUpdate !== null) {
      // 如果还有普通的更新未完成(即低优先级的任务),将错误的更新加入到链表之后,将错误捕获的更新放到低优先级的任务上渲染
      finishedQueue.lastUpdate.next = finishedQueue.firstCapturedUpdate;
      finishedQueue.lastUpdate = finishedQueue.lastCapturedUpdate;
    }
    // Clear the list of captured updates.
    // 如果没有低优先级任务,则清空本次渲染过程产生的捕获错误更新
    finishedQueue.firstCapturedUpdate = finishedQueue.lastCapturedUpdate = null;
  }

  // Commit the effects
  // 调用 effect.callback, 一般是 setState 的回调函数
  commitUpdateEffects(finishedQueue.firstEffect, instance);
  finishedQueue.firstEffect = finishedQueue.lastEffect = null;

  commitUpdateEffects(finishedQueue.firstCapturedEffect, instance);
  finishedQueue.firstCapturedEffect = finishedQueue.lastCapturedEffect = null;
}

function commitUpdateEffects<State>(
  effect: Update<State> | null,
  instance: any,
): void {
  while (effect !== null) {
    const callback = effect.callback;
    if (callback !== null) {
      effect.callback = null;
      // 执行 callback
      callCallback(callback, instance);
    }
    effect = effect.nextEffect;
  }
}

performSyncWork 和 perfromAsyncWork

performSyncWork 和 perfromAsyncWork

  • 都是调用的 performWork
  • 异步方式设置的 minExpirationTimeNoWork 0,通过判断 dl.didTimeout 是否过期,如果过期则设置 root.newExpirationTimeToWorkOn 为当前时间,表示这个任务可以直接执行,不需要判断是否超过帧时间
  • 同步方式设置的 minExpirationTimeSync 1

performAsyncWork

function performAsyncWork(dl) {
  // didTimeout 为 true 任务过期,立即执行
  if (dl.didTimeout) {
    if (firstScheduledRoot !== null) {
      // 设置当前渲染时间为当前时间
      recomputeCurrentRendererTime();
      let root: FiberRoot = firstScheduledRoot;
      do {
        // 标记 root 节点变量, 如果当前任务过期设置root.newExpirationTimeToWorkOn 为当前时间
        didExpireAtExpirationTime(root, currentRendererTime);
        // 找 下一个 root
        root = (root.nextScheduledRoot: any);
      } while (root !== firstScheduledRoot);
    }
  }
  performWork(NoWork, dl);
}

function performSyncWork() {
  performWork(Sync, null);
}

function didExpireAtExpirationTime(
  root: FiberRoot,
  currentTime: ExpirationTime,
): void {
  const expirationTime = root.expirationTime;
  // root 已经过期
  if (expirationTime !== NoWork && currentTime >= expirationTime) {
    // The root has expired. Flush all work up to the current time.
    //
    root.nextExpirationTimeToWorkOn = currentTime;
  }
}

performWork

  • 是否有 deadline 的区分异步/同步,用来判断一帧的渲染时间内还有没有时间留给 React 更新的

  • 循环渲染 Root 的条件:循环不同 root 和不同优先级任务来更新

  • 超过时间片的处理:deadline 这一帧的时间到了把执行权交回浏览器

  • shouldYield 方法用来判断一帧渲染时间内是否还有剩余时间留个React更新,改变全局变量 deadlineDidExpire,为 false 表示还有时间留给React更新

function shouldYield() {
  if (deadlineDidExpire) {
    return true;
  }
  // deadline.timeRemaining 返回的值表示一帧渲染时间还有剩余, 直接return false, 此时deadlineDidExpire 为false
  if (
    deadline === null ||
    deadline.timeRemaining() > timeHeuristicForUnitOfWork
  ) {
    // Disregard deadline.didTimeout. Only expired work should be flushed
    // during a timeout. This path is only hit for non-expired work.
    return false;
  }
  deadlineDidExpire = true;
  return true;
}
function performWork(minExpirationTime: ExpirationTime, dl: Deadline | null) {
  deadline = dl;

  // Keep working on roots until there's no more work, or until we reach
  // the deadline.
  findHighestPriorityRoot();

  // 异步
  if (deadline !== null) {
    // 重新计算当前渲染时间
    recomputeCurrentRendererTime();
    currentSchedulerTime = currentRendererTime;

    while (
      nextFlushedRoot !== null &&
      nextFlushedExpirationTime !== NoWork &&
      (minExpirationTime === NoWork ||
        minExpirationTime >= nextFlushedExpirationTime) &&
        // 还有剩余时间更新 或者 任务已经过期需要强制输出
      (!deadlineDidExpire || currentRendererTime >= nextFlushedExpirationTime)
    ) {
      performWorkOnRoot(
        nextFlushedRoot,
        nextFlushedExpirationTime,
        // 过期为 true,未过期为 false
        currentRendererTime >= nextFlushedExpirationTime,
      );
      findHighestPriorityRoot();
      recomputeCurrentRendererTime();
      currentSchedulerTime = currentRendererTime;
    }
  } else {
    // 同步
    while (
      // root 非空
      nextFlushedRoot !== null &&
      // root 有更新
      nextFlushedExpirationTime !== NoWork &&
      // minExpirationTime 为 Sync,那么就只能是处理 nextFlushedExpirationTime 为Sync的情况,即同步执行的情况
      (minExpirationTime === NoWork ||
        minExpirationTime >= nextFlushedExpirationTime)
    ) {
      performWorkOnRoot(nextFlushedRoot, nextFlushedExpirationTime, true);
      findHighestPriorityRoot();
    }
  }

  // We're done flushing work. Either we ran out of time in this callback,
  // or there's no more work left with sufficient priority.

  // If we're inside a callback, set this to false since we just completed it.
  if (deadline !== null) {
    callbackExpirationTime = NoWork;
    callbackID = null;
  }
  // If there's work left over, schedule a new callback.
  if (nextFlushedExpirationTime !== NoWork) {
    scheduleCallbackWithExpirationTime(
      ((nextFlushedRoot: any): FiberRoot),
      nextFlushedExpirationTime,
    );
  }

  // Clean-up.
  deadline = null;
  deadlineDidExpire = false;

  finishRendering();
}

findHighestPriorityRoot

  • 找到更新优先级最高的root
  • 并将 root 赋值给 nextFlushedRoot, root对应的 expirationTime 赋值给 nextFlushedExpirationTime
  • 默认只有一个 root 的情况下
firstScheduledRoot = lastScheduledRoot = root;
root.nextScheduledRoot = root;
function findHighestPriorityRoot() {
  let highestPriorityWork = NoWork;
  let highestPriorityRoot = null;
  // 有 root 的情况
  if (lastScheduledRoot !== null) {
    let previousScheduledRoot = lastScheduledRoot;
    let root = firstScheduledRoot;
    while (root !== null) {
      const remainingExpirationTime = root.expirationTime;
      // Nowork 表示节点没有更新
      if (remainingExpirationTime === NoWork) {
        // This root no longer has work. Remove it from the scheduler.
        // 只有一个 root 的情况
        if (root === root.nextScheduledRoot) {
          // This is the only root in the list.
          root.nextScheduledRoot = null;
          firstScheduledRoot = lastScheduledRoot = null;
          break;
        }
        // ....
      } else {
        // root 上有更新
        if (
          highestPriorityWork === NoWork ||
          remainingExpirationTime < highestPriorityWork
        ) {
          // Update the priority, if it's higher
          // 找更新优先级最高的 root 和它对应的 expirationTime
          highestPriorityWork = remainingExpirationTime;
          highestPriorityRoot = root;
        }
        if (root === lastScheduledRoot) {
          break;
        }
        if (highestPriorityWork === Sync) {
          // Sync is highest priority by definition so
          // we can stop searching.
          break;
        }
        previousScheduledRoot = root;
        root = root.nextScheduledRoot;
      }
    }
  }

  nextFlushedRoot = highestPriorityRoot;
  nextFlushedExpirationTime = highestPriorityWork;
}

performWorkonRoot

  • isRendering 置为 true,执行完后为 false
  • renderRoot 渲染阶段
  • completeRoot 提交(commit)阶段
    - finishedWork 是已经完成 renderRoot 渲染阶段的任务,只有 renderRoot 后才不为 null
  • 异步情况可以被打断,如果还有剩余时间更新,那么继续走 completeRoot 提交。没有剩余时间则等到下个时间段进入 performWorkOnRoot 判断
function performWorkOnRoot(
  root: FiberRoot,
  expirationTime: ExpirationTime,
  isExpired: boolean,
) {
  isRendering = true;

  // Check if this is async work or sync/expired work.
  // 同步任务 或者 过期任务
  if (deadline === null || isExpired) {
    // Flush work without yielding.
    // TODO: Non-yieldy work does not necessarily imply expired work. A renderer
    // may want to perform some work without yielding, but also without
    // requiring the root to complete (by triggering placeholders).

    let finishedWork = root.finishedWork;
    // 上一次 renderRoot 完,但没有时间 completeRoot 了
    if (finishedWork !== null) {
      // This root is already complete. We can commit it.
      completeRoot(root, finishedWork, expirationTime);
    } else {
      root.finishedWork = null;
      // If this root previously suspended, clear its existing timeout, since
      // we're about to try rendering again.
      // suspense 功能相关
      const timeoutHandle = root.timeoutHandle;
      if (timeoutHandle !== noTimeout) {
        root.timeoutHandle = noTimeout;
        // $FlowFixMe Complains noTimeout is not a TimeoutID, despite the check above
        cancelTimeout(timeoutHandle);
      }
      // 表示这个任务不可中断(同步或过期任务不可中断)
      const isYieldy = false;
     // renderRoot 之后赋值 finishedWork
      renderRoot(root, isYieldy, isExpired);
      finishedWork = root.finishedWork;
      if (finishedWork !== null) {
        // We've completed the root. Commit it.
        completeRoot(root, finishedWork, expirationTime);
      }
    }
  } else {
    // Flush async work.
    let finishedWork = root.finishedWork;
    if (finishedWork !== null) {
      // This root is already complete. We can commit it.
      completeRoot(root, finishedWork, expirationTime);
    } else {
      root.finishedWork = null;
      // If this root previously suspended, clear its existing timeout, since
      // we're about to try rendering again.
      const timeoutHandle = root.timeoutHandle;
      if (timeoutHandle !== noTimeout) {
        root.timeoutHandle = noTimeout;
        // $FlowFixMe Complains noTimeout is not a TimeoutID, despite the check above
        cancelTimeout(timeoutHandle);
      }
      // 异步任务是可以中断的
      const isYieldy = true;
     // renderRoot 之后赋值 finishedWork
      renderRoot(root, isYieldy, isExpired);
      finishedWork = root.finishedWork;
      // 可能被中断,被中断 finishedWork 为null
      if (finishedWork !== null) {
        // We've completed the root. Check the deadline one more time
        // before committing.
        // 还有剩余时间更新则继续 completeRoot
        if (!shouldYield()) {
          // Still time left. Commit the root.
          completeRoot(root, finishedWork, expirationTime);
        } else {
          // 没有时间更新了, 待到下个时间段进入 performWorkOnRoot 判断
          // There's no time left. Mark this root as complete. We'll come
          // back and commit it later.
          root.finishedWork = finishedWork;
        }
      }
    }
  }

  isRendering = false;
}

context

context

stack 概念

  • 更新节点时(beginWork)相关信息入栈
  • 完成节点时(completeUnitOfWork)相关信息出栈
  • 用不同的 cursor 记录不同的信息
  • 对 cursor 的操作: 入栈的时候设置 cursor.current 为新的值,出栈的时候设置 cursor.current 为上一个值
// ReactFiberStack.js

// 所有值存储的数组
const valueStack: Array<any> = [];
let index = -1;

function createCursor<T>(defaultValue: T): StackCursor<T> {
  return {
    current: defaultValue,
  };
}

function isEmpty(): boolean {
  return index === -1;
}

// 入栈
function push<T>(cursor: StackCursor<T>, value: T, fiber: Fiber): void {
  // index 累加
  index++;

  valueStack[index] = cursor.current;

  // 新的 value 不会推入栈
  cursor.current = value;
}

// 出栈
function pop<T>(cursor: StackCursor<T>, fiber: Fiber): void {
  if (index < 0) {
    return;
  }

  // 老的值赋值出来
  cursor.current = valueStack[index];
  // 清空 index 位置的值
  valueStack[index] = null;
  // 累减
  index--;
}
  • react 中有三种 cursor
    • context
      • contextStackCursor: 记录当前组件和父组件一起提供给子组件的 childContext 对象,默认是 {}
      • didPerformWorkStackCursor: 标记子树的 context 是否变化的, false 表示可以跳过更新,记录的值其实是 shouldUpdate
    • valueCursor: 新 context 的 cursor
// ReactFiberContext.js
// 老 context 的两种 cursor
const emptyContextObject = {};
// A cursor to the current merged context object on the stack.
let contextStackCursor: StackCursor<Object> = createCursor(emptyContextObject);
// A cursor to a boolean indicating whether the context has changed.
let didPerformWorkStackCursor: StackCursor<boolean> = createCursor(false);

// ReactFiberNewContext.js
// 新 context cursor
const valueCursor: StackCursor<mixed> = createCursor(null);

LegacyContext 老版本的 context

父组件提供 getChildContext 方法、声明 childContextTypes 属性,子组件声明 contextTypes 属性的方式获取 context 的问题

  • 会影响整个子树,影响性能
  • 嵌套的 context provider 会合并 key 相同的属性
  • 只有 classComponent 才能使用 getChildContext 方法给子树提供 childContext

updateHostRoot 对 FiberRoot 会执行第一次 context push,

function updateHostRoot(current, workInProgress, renderExpirationTime) {
  pushHostRootContext(workInProgress);
  // ...
}

function pushHostRootContext(workInProgress) {
  const root = (workInProgress.stateNode: FiberRoot);
  // 初次渲染如果有 pendingContext
  if (root.pendingContext) {
    pushTopLevelContextObject(
      workInProgress,
      root.pendingContext,
      root.pendingContext !== root.context,
    );
  } else if (root.context) {
    // Should always be set
    // 除了初次渲染 push 的值是 false,表明目前 context 没有变化
    pushTopLevelContextObject(workInProgress, root.context, false);
  }
  pushHostContainer(workInProgress, root.containerInfo);
}


function pushTopLevelContextObject(
  fiber: Fiber,
  context: Object,
  didChange: boolean,
): void {
  push(contextStackCursor, context, fiber);
  push(didPerformWorkStackCursor, didChange, fiber);
}

之后只有 classComponent 能提供 childContext, 在 updateClassComponent 中 push 子树的 context 对象,

// updateClassComponent

function updateClassComponent(
  current: Fiber | null,
  workInProgress: Fiber,
  Component: any,
  nextProps,
  renderExpirationTime: ExpirationTime,
) {
  // Push context providers early to prevent context stack mismatches.
  // During mounting we don't know the child context yet as the instance doesn't exist.
  // We will invalidate the child context in finishClassComponent() right after rendering.
  let hasContext;
  // 判断是否是老 context 方式提供 context ,如果是,则 push,并将hasContext 置为 true ,在 finishClassComponent 中会用到
  if (isLegacyContextProvider(Component)) {
    hasContext = true;
    pushLegacyContextProvider(workInProgress);
  } else {
    hasContext = false;
  }
  // 新的 context 相关
  prepareToReadContext(workInProgress, renderExpirationTime);

  // ...
}

function isContextProvider(type: Function): boolean {
  const childContextTypes = type.childContextTypes;
  return childContextTypes !== null && childContextTypes !== undefined;
}

pushContextProvider

  • 进入 updateComponentClass 时首先 push 一次老的 context
  • 将全局变量 previousContext 赋值为 contextStackCursor.current 即上一次 push 的值,这个值记录着父组件提供的 context 的集合
  • 在进入 updateComponentClass 时并不知道组件是否需要更新,并不知道是否有新的 contetxt
  • 只有在 finishClassComponent 中判断如果组件需要更新了,会重新计算新的 context,执行 invalidateContextProvider 将计算过后的 mergedContext 挂载在 __reactInternalMemoizedMergedChildContext 属性上
//
function pushContextProvider(workInProgress: Fiber): boolean {
  const instance = workInProgress.stateNode;
  // We push the context as early as possible to ensure stack integrity.
  // If the instance does not exist yet, we will push null at first,
  // and replace it on the stack later when invalidating the context.
  const memoizedMergedChildContext =
    (instance && instance.__reactInternalMemoizedMergedChildContext) ||
    emptyContextObject;

  // Remember the parent context so we can merge with it later.
  // Inherit the parent's did-perform-work value to avoid inadvertently blocking updates.
  // 上一次 push 时的值,即对应父组件的 context
  previousContext = contextStackCursor.current;
  push(contextStackCursor, memoizedMergedChildContext, workInProgress);
  push(
    didPerformWorkStackCursor,
    didPerformWorkStackCursor.current,
    workInProgress,
  );

  return true;
}

invalidateContextProvider

  • finishClassComponent 中如果不需要更新且没有错误则跳过更新,并且如果 hasContexttrue 则会执行 invalidateContextProvider,这里 didChangefalse,即对应 pushdidPerformWorkStackCursor 值为 false,表示组件可以跳过更新,在往下执行子树的 beginWork 时就会根据 !hasContextChanged() 判断是否可以跳过更新
  • finishClassComponent 如果组件需要更新,也会执行 invalidateContextProvider,这里 didChangetrue
  • PureComponent 不会判断 context 是否变化
// finishClassComponent

function finishClassComponent(
  current: Fiber | null,
  workInProgress: Fiber,
  Component: any,
  shouldUpdate: boolean,
  hasContext: boolean,
  renderExpirationTime: ExpirationTime,
) {
  // Refs should update even if shouldComponentUpdate returns false
  markRef(current, workInProgress);

  const didCaptureError = (workInProgress.effectTag & DidCapture) !== NoEffect;

  // 不需要更新且没有错误则跳过更新
  if (!shouldUpdate && !didCaptureError) {
    // Context providers should defer to sCU for rendering
    // updateClassComponent 中使用了老 context 后会赋值为 true
    // didChange 为 false
    if (hasContext) {
      invalidateContextProvider(workInProgress, Component, false);
    }
    // 跳过更新
    return bailoutOnAlreadyFinishedWork(
      current,
      workInProgress,
      renderExpirationTime,
    );
  }
  // ...
  if (hasContext) {
    invalidateContextProvider(workInProgress, Component, true);
  }

  return workInProgress.child;
}

// invalidateContextProvider

function invalidateContextProvider(
  workInProgress: Fiber,
  type: any,
  didChange: boolean, // 组件是否有更新
): void {
  const instance = workInProgress.stateNode;

  // 组件需要更新
  if (didChange) {
    // Merge parent and own context.
    // Skip this if we're not updating due to sCU.
    // This avoids unnecessarily recomputing memoized values.
    // 重新计算新的合并过的 context,(会合并父组件的 context )
    const mergedContext = processChildContext(
      workInProgress,
      type,
      previousContext, // 上一次 push 的 context 的值
    );
    // 挂载到 __reactInternalMemoizedMergedChildContext 属性上
    instance.__reactInternalMemoizedMergedChildContext = mergedContext;

    // Replace the old (or empty) context with the new one.
    // It is important to unwind the context in the reverse order.
    // finishClassComponent 之前在 updateClassComponent 的时候 push 了一次老的,如果有更新,需要将老的 pop
    pop(didPerformWorkStackCursor, workInProgress);
    pop(contextStackCursor, workInProgress);
    // Now push the new context and mark that it has changed.
    // 再 push 新计算出来的 context
    push(contextStackCursor, mergedContext, workInProgress);
    push(didPerformWorkStackCursor, didChange, workInProgress);
  } else {
    // 没有更新,有没有更新都需要将 didPerformWorkStackCursor.current 同步 didChange, 如果不同步 false 的情况那组件 beginWork 时会一直更新,没有可以跳过更新的情况
    // pop 老的
    pop(didPerformWorkStackCursor, workInProgress);
    // push 新的
    // 仍需要 push didChange 将 didPerformWorkStackCursor.current = false
    push(didPerformWorkStackCursor, didChange, workInProgress);
  }
}

processChildContext

  • 合并父组件的 context ,相同 key 属性会被覆盖
function processChildContext(
  fiber: Fiber,
  type: any,
  parentContext: Object, // 全局变量 previousContext
): Object {
  const instance = fiber.stateNode;
  const childContextTypes = type.childContextTypes;

  // TODO (bvaughn) Replace this behavior with an invariant() in the future.
  // It has only been added in Fiber to match the (unintentional) behavior in Stack.
  // 如果没有 getChildContext 方法就直接返回父组件的 context
  if (typeof instance.getChildContext !== 'function') {

    return parentContext;
  }

  let childContext;
  // 获取新的 context
  childContext = instance.getChildContext();

  // 合并 context
  return {...parentContext, ...childContext};
}

hasContextChanged

  • beginWork 开始时会调用这个方法根据 context 是否变化来判断是否可以跳过组件更新,这也是影响性能的一个地方
  • !hasLegacyContextChanged() 即为 false 时跳过更新,这个值实际存储的是 didChange,跟 shouldComponentUpdate 有关
  • 没有 context 变化,各个类型的组件分别 push context
function hasContextChanged(): boolean {
  return didPerformWorkStackCursor.current;
}

// beginWork
function beginWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderExpirationTime: ExpirationTime,
): Fiber | null {
  const updateExpirationTime = workInProgress.expirationTime;

  // 非首次渲染
  if (current !== null) {
    const oldProps = current.memoizedProps;
    const newProps = workInProgress.pendingProps;
    if (
      oldProps === newProps && // 新老 props 相等
      !hasLegacyContextChanged() && // 父组件的 context 没有变化
      (updateExpirationTime === NoWork ||
        updateExpirationTime > renderExpirationTime)
    ) {
      // This fiber does not have any pending work. Bailout without entering
      // the begin phase. There's still some bookkeeping we that needs to be done
      // in this optimized path, mostly pushing stuff onto the stack.
      switch (workInProgress.tag) {
        case HostRoot:
          pushHostRootContext(workInProgress);
          resetHydrationState();
          break;
        case HostComponent:
          pushHostContext(workInProgress);
          break;
        case ClassComponent: {
          const Component = workInProgress.type;
          // 有 childContextTypes 属性
          if (isLegacyContextProvider(Component)) {
            // 入栈
            pushLegacyContextProvider(workInProgress);
          }
          break;
        }
        case HostPortal:
          pushHostContainer(
            workInProgress,
            workInProgress.stateNode.containerInfo,
          );
          break;
        case ContextProvider: {
          const newValue = workInProgress.memoizedProps.value;
          pushProvider(workInProgress, newValue);
          break;
        }
        case Profiler:
          if (enableProfilerTimer) {
            workInProgress.effectTag |= Update;
          }
          break;
        case SuspenseComponent: {
          // ...
          break;
        }
      }
      // 跳过更新
      return bailoutOnAlreadyFinishedWork(
        current,
        workInProgress,
        renderExpirationTime,
      );
    }
  }
  // ....
}

completeWork 中 pop context

  • popContext: pop 的顺序和 push 时相反,最后 pushdidPerformWorkStackCursorpop,确保从 valueStack 中取值正确
function completeWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderExpirationTime: ExpirationTime,
): Fiber | null {
  const newProps = workInProgress.pendingProps;

  switch (workInProgress.tag) {
    case IndeterminateComponent:
      break;
    case LazyComponent:
      break;
    case SimpleMemoComponent:
    case FunctionComponent:
      break;
    case ClassComponent: {
      const Component = workInProgress.type;
      if (isLegacyContextProvider(Component)) {
        popLegacyContext(workInProgress);
      }
      break;
    }
    // ....
  }
  // ...
}

// popLegacyContext => popContext

function popContext(fiber: Fiber): void {
  pop(didPerformWorkStackCursor, fiber);
  pop(contextStackCursor, fiber);
}

子组件从父组件提供的 context 对象里获取需要的属性

  • getUnmaskedContext 获取合并过的 context
  • getMaskedContextgetUnmaskedContext 的结果里取需要的属性
// updateFunctionComponent
function updateFunctionComponent(
  current,
  workInProgress,
  Component,
  nextProps: any,
  renderExpirationTime,
) {

  const unmaskedContext = getUnmaskedContext(workInProgress, Component, true);
  const context = getMaskedContext(workInProgress, unmaskedContext);

  let nextChildren;
  prepareToReadContext(workInProgress, renderExpirationTime);
  // ...
  return workInProgress.child;
}

// updateClassInstance
function updateClassInstance(
  current: Fiber,
  workInProgress: Fiber,
  ctor: any,
  newProps: any,
  renderExpirationTime: ExpirationTime,
): boolean {
  const instance = workInProgress.stateNode;

  const oldProps = workInProgress.memoizedProps;
  instance.props = oldProps;

  const oldContext = instance.context;
  // 新的 context api 使用方式
  const contextType = ctor.contextType;
  let nextContext;
  if (typeof contextType === 'object' && contextType !== null) {
    nextContext = readContext(contextType);
  } else {
    // 老的 context
    // 组件自己声明的 contextTypes 是给组件的子节点用的,自己要使用 context 来更新,应该用 parent 提供的 context,即 previousContext
    // 如果自己也是 context provider,且已经 push 过一次了,这里应该读 previousContext
    const nextUnmaskedContext = getUnmaskedContext(workInProgress, ctor, true);
    // 子组件从父组件合并过后的 context 中选择性属性
    nextContext = getMaskedContext(workInProgress, nextUnmaskedContext);
  }
  // ...
}

getUnmaskedContext

function getUnmaskedContext(
  workInProgress: Fiber,
  Component: Function,
  didPushOwnContextIfProvider: boolean, // 读 context 一般都为 true
): Object {
  // 如果自己也是 context provider,且已经 push 过一次了,这里应该读 previousContext
  if (didPushOwnContextIfProvider && isContextProvider(Component)) {
    // If the fiber is a context provider itself, when we read its context
    // we may have already pushed its own child context on the stack. A context
    // provider should not "see" its own child context. Therefore we read the
    // previous (parent) context instead for a context provider.
    return previousContext;
  }
  // 不是 provider 则读当前的 cursor 就可以
  // 或者 mountIndeterminateComponent 函数里 didPushOwnContextIfProvider 为 false
  return contextStackCursor.current;
}

getMaskedContext

  • context 中获取组件声明的 contextTypes 中需要的数据
function getMaskedContext(
  workInProgress: Fiber,
  unmaskedContext: Object,
): Object {
  const type = workInProgress.type;

  const contextTypes = type.contextTypes;
  if (!contextTypes) {
    return emptyContextObject;
  }

  // Avoid recreating masked context unless unmasked context has changed.
  // Failing to do this will result in unnecessary calls to componentWillReceiveProps.
  // This may trigger infinite loops if componentWillReceiveProps calls setState.
  const instance = workInProgress.stateNode;
  // 如果有缓存则从缓存里读
  if (
    instance &&
    instance.__reactInternalMemoizedUnmaskedChildContext === unmaskedContext
  ) {
    return instance.__reactInternalMemoizedMaskedChildContext;
  }

  const context = {};
  // 根据 contextTypes 上声明的 key 读取父组件合并过后的 context 中的值
  for (let key in contextTypes) {
    context[key] = unmaskedContext[key];
  }

  // Cache unmasked context so we can avoid recreating masked context unless necessary.
  // Context is created before the class component is instantiated so check for instance.
  // 缓存 context
  if (instance) {
    cacheContext(workInProgress, unmaskedContext, context);
  }

  return context;
}

// 缓存
function cacheContext(
  workInProgress: Fiber,
  unmaskedContext: Object,
  maskedContext: Object,
): void {
  const instance = workInProgress.stateNode;
  instance.__reactInternalMemoizedUnmaskedChildContext = unmaskedContext;
  instance.__reactInternalMemoizedMaskedChildContext = maskedContext;
}

新 context

  • 组件化的使用方式 Context.Provider Context.Consumer
  • context 的提供方和订阅方都是独立的
  • 没有附带的性能影响

ReactContext

  • 创建 context,并创建 Provider 组件类型,Consumer组件类型即创建的 context
  • ContextConsumer = REACT_CONTEXT_TYPE;
function createContext<T>(
  defaultValue: T,
  calculateChangedBits: ?(a: T, b: T) => number,
): ReactContext<T> {

  const context: ReactContext<T> = {
    $$typeof: REACT_CONTEXT_TYPE,
    _calculateChangedBits: calculateChangedBits,
    // As a workaround to support multiple concurrent renderers, we categorize
    // some renderers as primary and others as secondary. We only expect
    // there to be two concurrent renderers at most: React Native (primary) and
    // Fabric (secondary); React DOM (primary) and React ART (secondary).
    // Secondary renderers store their context values on separate fields.
    _currentValue: defaultValue,
    _currentValue2: defaultValue,
    // These are circular
    Provider: (null: any),
    Consumer: (null: any),
  };

  context.Provider = {
    $$typeof: REACT_PROVIDER_TYPE,
    _context: context,
  };

  let hasWarnedAboutUsingNestedContextConsumers = false;
  let hasWarnedAboutUsingConsumerProvider = false;

  if (__DEV__) {
    // ....
  } else {
    context.Consumer = context;
  }

  if (__DEV__) {
    context._currentRenderer = null;
    context._currentRenderer2 = null;
  }

  return context;
}

updateContextProvider

  • pushProvider 的时候设置 context._currentValue = nextValue; 最后 consumer 是从这个值中读取的
// beginWork

case ContextProvider:
  return updateContextProvider(
    current,
    workInProgress,
    renderExpirationTime,
  );

function updateContextProvider(
  current: Fiber | null,
  workInProgress: Fiber,
  renderExpirationTime: ExpirationTime,
) {
  const providerType: ReactProviderType<any> = workInProgress.type;
  // _context 指向创建的 context 对象(也是 Consumer)
  const context: ReactContext<any> = providerType._context;

  const newProps = workInProgress.pendingProps;
  const oldProps = workInProgress.memoizedProps;

  const newValue = newProps.value;
  // push 新的 value
  pushProvider(workInProgress, newValue);

  if (oldProps !== null) {
    const oldValue = oldProps.value;
    // 模拟 Object.is
    // 如果 newValue, oldValue 相等返回 0
    // 如果不相等则返回 MAX_SIGNED_31_BIT_INT
    const changedBits = calculateChangedBits(context, newValue, oldValue);
    if (changedBits === 0) {
      // No change. Bailout early if children are the same.
      if (
        oldProps.children === newProps.children &&
        !hasLegacyContextChanged()
      ) {
        // newValue, oldValue相等 且新老 children 相同,且没有老 context变化的条件下,可以跳过更新
        return bailoutOnAlreadyFinishedWork(
          current,
          workInProgress,
          renderExpirationTime,
        );
      }
    } else {
      // The context value changed. Search for matching consumers and schedule
      // them to update.
      // 有更新,则遍历子节点对有 firstContextDependency 的 classComponent 类型的节点创建一个 forceUpdate 的更新
      propagateContextChange(
        workInProgress,
        context,
        changedBits,
        renderExpirationTime,
      );
    }
  }

  const newChildren = newProps.children;
  // 调和子节点
  reconcileChildren(current, workInProgress, newChildren, renderExpirationTime);
  return workInProgress.child;
}

pushProvider

function pushProvider<T>(providerFiber: Fiber, nextValue: T): void {
  // context 对象
  const context: ReactContext<T> = providerFiber.type._context;

  // ReactDOM 下 isPrimaryRenderer 为 true
  if (isPrimaryRenderer) {
    // push valueCursor 值为 context._currentValue
    // valueCursor 记录的是当前这棵树下面一共经历了几个 provider 对应的值
    push(valueCursor, context._currentValue, providerFiber);
  // consumer 读取值是从 context._currentValue 读取的,与 v alueCursor 无关
    context._currentValue = nextValue;
  } else {
  }
}

propagateContextChange

  • 更新 Provider 组件
  • 遍历子树的每个一个节点,找到有 firstContextDependency 的节点,如果节点依赖的 context 有更新(非 setState 的更新),对于 classComponent 类型主动创建一个 tagforceUpdateupdate 来强制更新,并操作 expirationTime 和父节点的 childExpirationTime
  • firstContextDependency 是在 updateContextConsumerprepareToReadContext 方法初始化,在 readContext 中被赋值
function propagateContextChange(
  workInProgress: Fiber,
  context: ReactContext<mixed>,
  changedBits: number,
  renderExpirationTime: ExpirationTime,
): void {
  // 第一个子节点
  let fiber = workInProgress.child;
  if (fiber !== null) {
    // Set the return pointer of the child to the work-in-progress fiber.
    fiber.return = workInProgress;
  }
  while (fiber !== null) {
    let nextFiber;

    // Visit this fiber.
    // firstContextDependency 这个属性在 updaterConsumer、 updateClassComponent、updateFunctionComponent 中的 prepareToReadContext 中被初始化
    let dependency = fiber.firstContextDependency;
    if (dependency !== null) {
      do {
        // Check if the context matches.
        if (
          // 表示遍历过程中的这个组件依赖于 context,如果 context 变化则需要重新渲染
          dependency.context === context &&
          // dependency.observedBits 只要不是 0 则 这个条件为真, 在 updateContextConsumer 中一般默认设置为 MAX_SIGNED_31_BIT_INT
          // changedBits 有变化的情况则值为 MAX_SIGNED_31_BIT_INT
          // 同时 dependency.observedBits 和 changedBits 有相交的部分则说明依赖的部分也变化了
          (dependency.observedBits & changedBits) !== 0
        ) {
          // 满足以上条件,组件需要更新
          // Match! Schedule an update on this fiber.

          if (fiber.tag === ClassComponent) {
            // Schedule a force update on the work-in-progress.
            // context 更新,但没有 setState 的创建 update ,则主动创建一个 update,tag 为 ForceUpdate 强制更新
            const update = createUpdate(renderExpirationTime);
            update.tag = ForceUpdate;
            // TODO: Because we don't have a work-in-progress, this will add the
            // update to the current fiber, too, which means it will persist even if
            // this render is thrown away. Since it's a race condition, not sure it's
            // worth fixing.
            enqueueUpdate(fiber, update);
          }
          // 创建 update 的同时,操作 expirationTime
          if (
            fiber.expirationTime === NoWork ||
            fiber.expirationTime > renderExpirationTime // 节点的更新优先级低于当前更新的优先级
          ) {
            // 则重新赋值节点的expirationTime 保证在这一次更新中能更新到
            fiber.expirationTime = renderExpirationTime;
          }
          let alternate = fiber.alternate;
          // 同步 alternate
          if (
            alternate !== null &&
            (alternate.expirationTime === NoWork ||
              alternate.expirationTime > renderExpirationTime)
          ) {
            alternate.expirationTime = renderExpirationTime;
          }
          // Update the child expiration time of all the ancestors, including
          // the alternates.
          //  同步父节点的 childExpirationTime
          let node = fiber.return;
          while (node !== null) {
            alternate = node.alternate;
            if (
              node.childExpirationTime === NoWork ||
              node.childExpirationTime > renderExpirationTime
            ) {
              node.childExpirationTime = renderExpirationTime;
              if (
                alternate !== null &&
                (alternate.childExpirationTime === NoWork ||
                  alternate.childExpirationTime > renderExpirationTime)
              ) {
                alternate.childExpirationTime = renderExpirationTime;
              }
            } else if (
              alternate !== null &&
              (alternate.childExpirationTime === NoWork ||
                alternate.childExpirationTime > renderExpirationTime)
            ) {
              alternate.childExpirationTime = renderExpirationTime;
            } else {
              // Neither alternate was updated, which means the rest of the
              // ancestor path already has sufficient priority.
              break;
            }
            node = node.return;
          }
        }
        nextFiber = fiber.child;
        // dependency 也是单链表结构
        dependency = dependency.next;
      } while (dependency !== null);
    } else if (fiber.tag === ContextProvider) {
      // Don't scan deeper if this is a matching provider
      nextFiber = fiber.type === workInProgress.type ? null : fiber.child;
    } else {
      // 往 child 找
      // Traverse down.
      nextFiber = fiber.child;
    }
    // 遍历子树的每个一个节点,找到有 firstContextDependency 的然后对它创建 update
    if (nextFiber !== null) {
      // Set the return pointer of the child to the work-in-progress fiber.
      nextFiber.return = fiber;
    } else {
      // No child. Traverse to next sibling.
      nextFiber = fiber;
      // 如果没有 child 则往兄弟节点找
      while (nextFiber !== null) {
        if (nextFiber === workInProgress) {
          // We're back to the root of this subtree. Exit.
          nextFiber = null;
          break;
        }
        let sibling = nextFiber.sibling;
        if (sibling !== null) {
          // Set the return pointer of the sibling to the work-in-progress fiber.
          sibling.return = nextFiber.return;
          nextFiber = sibling;
          break;
        }
        // No more siblings. Traverse up.
        nextFiber = nextFiber.return;
      }
    }
    fiber = nextFiber;
  }
}

updateContextConsumer

  • workInProgress.typecreateContext 返回的 context 对象
  • prepareToReadContext 初始化 firstContextDependency,赋值全局变量 currentlyRenderingFiber 为当前 workInProgress
case ContextConsumer:
  return updateContextConsumer(
    current,
    workInProgress,
    renderExpirationTime,
  );

function updateContextConsumer(
  current: Fiber | null,
  workInProgress: Fiber,
  renderExpirationTime: ExpirationTime,
) {
  let context: ReactContext<any> = workInProgress.type;
  const newProps = workInProgress.pendingProps;
  // consumer 的 children 为一个函数,这个函数的参数即 provider 提供的 value
  const render = newProps.children;

  // 初始化 currentlyRenderingFiber 、workInProgress.firstContextDependency
  prepareToReadContext(workInProgress, renderExpirationTime);
  // 读取 context._currentValue ,
  const newValue = readContext(context, newProps.unstable_observedBits);
  let newChildren;
  if (__DEV__) {
   // ...
  } else {
    // 调用 render 获取children
    newChildren = render(newValue);
  }

  // React DevTools reads this flag.
  workInProgress.effectTag |= PerformedWork;
  // 调和子节点
  reconcileChildren(current, workInProgress, newChildren, renderExpirationTime);
  return workInProgress.child;
}

prepareToReadContext

  • 往当前 fiber 上添加 firstContextDependency 属性,值为 null
  • 设置全局变量 currentlyRenderingFiber 为当前 fiber 节点
  • 设置全局变量 lastContextDependency 值为 null,用于构建单链表结构
function prepareToReadContext(
  workInProgress: Fiber,
  renderExpirationTime: ExpirationTime,
): void {
  currentlyRenderingFiber = workInProgress;
  lastContextDependency = null;
  lastContextWithAllBitsObserved = null;

  // Reset the work-in-progress list
  workInProgress.firstContextDependency = null;
}

readContext

  • 构建 firstContextDependencylastContextDependency 的 单链表结构
  • 获取 context._currentValue 做为 context
function readContext<T>(
  context: ReactContext<T>,
  observedBits: void | number | boolean, // => undefined
): T {
  // 首次进入这个函数 lastContextWithAllBitsObserved 为 null
  if (lastContextWithAllBitsObserved === context) {
    // Nothing to do. We already observe everything in this context.
    // unstable_observedBits 目前版本下为 undefined
  } else if (observedBits === false || observedBits === 0) {
    // Do not observe any updates.
  } else {
    let resolvedObservedBits; // Avoid deopting on observable arguments or heterogeneous types.
    if (
      typeof observedBits !== 'number' ||
      observedBits === MAX_SIGNED_31_BIT_INT
    ) {
      // Observe all updates.
      // 赋值 lastContextWithAllBitsObserved 为当前 context
      lastContextWithAllBitsObserved = ((context: any): ReactContext<mixed>);
      // 赋值 resolvedObservedBits ,
      resolvedObservedBits = MAX_SIGNED_31_BIT_INT;
    } else {
      resolvedObservedBits = observedBits;
    }

    let contextItem = {
      context: ((context: any): ReactContext<mixed>),
      // 这个值即在 updateContextProvider 中的 propagateContextChange 函数中判断 (dependency.observedBits & changedBits) !== 0
      observedBits: resolvedObservedBits,
      next: null,
    };

    if (lastContextDependency === null) {
      // This is the first dependency in the list
      // 构建 firstContextDependency 到 lastContextDependency 的 单链表结构
      currentlyRenderingFiber.firstContextDependency = lastContextDependency = contextItem;
    } else {
      // Append a new context item.
      lastContextDependency = lastContextDependency.next = contextItem;
    }
  }
  // ReactDOM 下 isPrimaryRenderer 为 true,直接返回 context._currentValue
  return isPrimaryRenderer ? context._currentValue : context._currentValue2;
}
  • updateFunctionComponent、updateClassComponent 也会执行 prepareToReadContext 这个方法,对于 classComponent 组件还可以声明 contextType 的方法来获取新 context,在 constructClassInstance、mountClassInstance、resumeMountClassInstance、updateClassInstance 方法中会判断 contextType 是否存在,如果存在则在赋值 instance.context = readContext(contextType), 从而能在声明周期方法里通过 this.context 获取 context
// prepareToReadContext

function updateFunctionComponent(
  current,
  workInProgress,
  Component,
  nextProps: any,
  renderExpirationTime,
) {
  const unmaskedContext = getUnmaskedContext(workInProgress, Component, true);
  const context = getMaskedContext(workInProgress, unmaskedContext);

  let nextChildren;
  prepareToReadContext(workInProgress, renderExpirationTime);
  //....
}

function updateClassComponent(
  current: Fiber | null,
  workInProgress: Fiber,
  Component: any,
  nextProps,
  renderExpirationTime: ExpirationTime,
) {
  // Push context providers early to prevent context stack mismatches.
  // During mounting we don't know the child context yet as the instance doesn't exist.
  // We will invalidate the child context in finishClassComponent() right after rendering.
  let hasContext;
  if (isLegacyContextProvider(Component)) {
    hasContext = true;
    pushLegacyContextProvider(workInProgress);
  } else {
    hasContext = false;
  }
  // 新的 context 相关, 设置 workInProgress.firstContextDependency 为null
  prepareToReadContext(workInProgress, renderExpirationTime);

  // ....
}

// readContext
function constructClassInstance(
  workInProgress: Fiber,
  ctor: any,
  props: any,
  renderExpirationTime: ExpirationTime,
): any {
  let isLegacyContextConsumer = false;
  let unmaskedContext = emptyContextObject;
  let context = null;
  const contextType = ctor.contextType;
  if (typeof contextType === 'object' && contextType !== null) {
    // 新 context 获取方式
    context = readContext((contextType: any));
  } else {
    unmaskedContext = getUnmaskedContext(workInProgress, ctor, true);
    const contextTypes = ctor.contextTypes;
    isLegacyContextConsumer =
      contextTypes !== null && contextTypes !== undefined;
    context = isLegacyContextConsumer
      ? getMaskedContext(workInProgress, unmaskedContext)
      : emptyContextObject;
  }
    // 往 instance 上挂载 context
    const instance = new ctor(props, context);
    // ....
}

function mountClassInstance(
  workInProgress: Fiber,
  ctor: any,
  newProps: any,
  renderExpirationTime: ExpirationTime,
): void {
  if (__DEV__) {
    checkClassInstance(workInProgress, ctor, newProps);
  }

  const instance = workInProgress.stateNode;
  instance.props = newProps;
  instance.state = workInProgress.memoizedState;
  instance.refs = emptyRefsObject;

  const contextType = ctor.contextType;
  if (typeof contextType === 'object' && contextType !== null) {
    // 挂载 context
    instance.context = readContext(contextType);
  } else {
    const unmaskedContext = getUnmaskedContext(workInProgress, ctor, true);
    instance.context = getMaskedContext(workInProgress, unmaskedContext);
  }
}

function resumeMountClassInstance(
  workInProgress: Fiber,
  ctor: any,
  newProps: any,
  renderExpirationTime: ExpirationTime,
): boolean {
  const instance = workInProgress.stateNode;

  const oldProps = workInProgress.memoizedProps;
  instance.props = oldProps;

  const oldContext = instance.context;
  const contextType = ctor.contextType;
  let nextContext;
  if (typeof contextType === 'object' && contextType !== null) {
    // 挂载 context
    nextContext = readContext(contextType);
  } else {
    const nextLegacyUnmaskedContext = getUnmaskedContext(
      workInProgress,
      ctor,
      true,
    );
    nextContext = getMaskedContext(workInProgress, nextLegacyUnmaskedContext);
  }
}

React创建更新

React 创建更新的过程

创建更新的方式

  • 初始渲染

    ReactDOM.render 、ReactDOM.hydrate

  • 组件内更新

    setState

    forceUpdate

    replaceState(即将被舍弃)

ReactDom.render

步骤

  • 创建 ReactRoot 包含React整个应用最顶点的对象
  • 创建 FiberRoot 和 RootFiber
  • 创建更新 update,用来更新调度,进入调度后 setState 或 ReactDOM.render 的后续操作都的调度器去管理的

初始渲染

ReactDOM对象有renderhydrate两个方法,render 方法是用在浏览器环境内, hydrate 用于服务端渲染,区别在于调用 legacyRenderSubtreeIntoContainer 方法第四个参数 falsetrue.

  const ReactDOM = {
    // ...
    hydrate(element: React$Node, container: DOMContainer, callback: ?Function) {
      // TODO: throw or warn if we couldn't hydrate?
      return legacyRenderSubtreeIntoContainer(
        null,
        element,
        container,
        true,
        callback,
      );
    },

    render(
      element: React$Element<any>, // React element
      container: DOMContainer, // 挂在节点
      callback: ?Function, // 渲染结束的callback
    ) {
      return legacyRenderSubtreeIntoContainer(
        null,
        element,
        container,
        false,
        callback,
      );
    }
  }

legacyRenderSubtreeIntoContainer 作用主要是生成 ReactRoot 并调用 ReactRoot 实例的 render 方法

  function legacyRenderSubtreeIntoContainer(
    parentComponent: ?React$Component<any, any>,
    children: ReactNodeList,
    container: DOMContainer,
    forceHydrate: boolean,
    callback: ?Function,
  ) {
    ...
    // TODO: Without `any` type, Flow says "Property cannot be accessed on any
    // member of intersection type." Whyyyyyy.
    let root: Root = (container._reactRootContainer: any);
    // container 就是首次渲染传入的<div id="root">, 这个dom 肯定不存在 _reactRootContainer 属性
    if (!root) {
      // Initial mount
      // 初次渲染,创建ReactRoot,并创建FiberRoot
      root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
        container,
        forceHydrate,
      );
      // 封装回调
      if (typeof callback === 'function') {
       ...
      }
      // Initial mount should not be batched.
      // 初始渲染不是批量更新
      DOMRenderer.unbatchedUpdates(() => {
         // render, hydrate 传入的 parentComponent 都是 null
        if (parentComponent != null) {
          root.legacy_renderSubtreeIntoContainer(
            parentComponent,
            children,
            callback,
          );
        } else {
          // 调用 ReactRoot 的render方法
          root.render(children, callback);
        }
      });
    } else {
      // 更新逻辑
      if (typeof callback === 'function') {
        const originalCallback = callback;
        callback = function() {
          const instance = DOMRenderer.getPublicRootInstance(root._internalRoot);
          originalCallback.call(instance);
        };
      }
      // Update
      if (parentComponent != null) {
        root.legacy_renderSubtreeIntoContainer(
          parentComponent,
          children,
          callback,
        );
      } else {
        root.render(children, callback);
      }
    return DOMRenderer.getPublicRootInstance(root._internalRoot);
  }

legacyCreateRootFromDOMContainer 函数的作用判断是不是 hydrate 渲染,ReactDOM.render 当然为 false,接着会判断 rootElement 有没有 ROOT_ATTRIBUTE_NAMEdata-reactroot 属性来判断是不是服务端渲染,如果不是则 while 循环将 root 所有子节点全部删掉,返回new ReactRoot(container, isConcurrent, shouldHydrate)

  function legacyCreateRootFromDOMContainer(
    container: DOMContainer, // ReactDOM.render 传入的 root
    forceHydrate: boolean, // ReactDOM.render 为false
  ): Root {
    const shouldHydrate =
      forceHydrate || shouldHydrateDueToLegacyHeuristic(container);
    // First clear any existing content.
    if (!shouldHydrate) {
      let warned = false;
      let rootSibling;
      while ((rootSibling = container.lastChild)) {
        if (__DEV__) {
          ...
        }
        // 把 root 的所有子节点删掉
        container.removeChild(rootSibling);
      }
    }
    if (__DEV__) {
      ...
    }
    // Legacy roots are not async by default.
    // root 节点创建时同步的,isConcurrent: false
    const isConcurrent = false;
    return new ReactRoot(container, isConcurrent, shouldHydrate);
  }

接着看ReactRoot构造函数,构造函数里调用了 ReactFiberReconciler 里的 createContainer 方法,这个方法调用 createFiberRoot 生成一个 FiberRoot 对象,最后挂载到实例的 _internalRoot 上,

  function ReactRoot(
    container: Container,
    isConcurrent: boolean,
    hydrate: boolean,
  ) {
    const root = DOMRenderer.createContainer(container, isConcurrent, hydrate);
    this._internalRoot = root;
  }

  // ReactFiberReconciler.js
  export function createContainer(
    containerInfo: Container,
    isConcurrent: boolean,
    hydrate: boolean,
  ): OpaqueRoot {
    return createFiberRoot(containerInfo, isConcurrent, hydrate);
  }

legacyRenderSubtreeIntoContainer 方法里 root.render 调用的最终 ReactRoot 构造函数的 protoType 上定义了 render 方法,而它调用的是 ReactFiberReconciler 里的 updateContainer

  ReactRoot.prototype.render = function(
    children: ReactNodeList,
    callback: ?() => mixed,
  ): Work {
    // FiberRoot
    // FiberRoot 是根据 ReactDOM.render 方法的第二个参数创建出来的,创建的过程中同时创建 RootFiber, RootFiber.stateNode = FiberRoot, FiberRoot.current = RootFiber
    const root = this._internalRoot;
    const work = new ReactWork();
    callback = callback === undefined ? null : callback;
    if (__DEV__) {
      warnOnInvalidCallback(callback, 'render');
    }
    if (callback !== null) {
      work.then(callback);
    }
    DOMRenderer.updateContainer(children, root, null, work._onCommit);
    return work;
  };

DOMRenderer.updateContainer 中计算出一个 expirationTime 传入了 updateContainerAtExpirationTime.

  // ReactFiberReconciler.js
  export function updateContainer(
    element: ReactNodeList, // <App />
    container: OpaqueRoot, // FiberRoot
    parentComponent: ?React$Component<any, any>,
    callback: ?Function,
  ): ExpirationTime {
    // RootFiber
    const current = container.current;
    // 创建一个时间差
    const currentTime = requestCurrentTime();
    // 计算出一个时间,ConcurrentMode 会用到
    const expirationTime = computeExpirationForFiber(currentTime, current);
    return updateContainerAtExpirationTime(
      element,
      container,
      parentComponent,
      expirationTime,
      callback,
    );
  }

FiberRootRootFiber 接下来会讲到,先看 updateContainerAtExpirationTime 创建更新的过程,调用scheduleRootUpdate 方法将 RootFiberelement、计算出的expirationTimecallback 传入,在这个方法里,首先会调用createUpdate 创建一个 update 对象,enqueueUpdate 方法将 update 对象加入到 fiber 对象上的 updateQueue 里,scheduleWork 即开始执行调度。创建更新的过程到此为止。

  export function updateContainerAtExpirationTime(
    element: ReactNodeList, // <App />
    container: OpaqueRoot,
    parentComponent: ?React$Component<any, any>,
    expirationTime: ExpirationTime,
    callback: ?Function,
  ) {
    // TODO: If this is a nested container, this won't be the root.
    const current = container.current;

    if (__DEV__) {
      ...
    }
    // 目前版本React拿不到context,因为 parentComponent 为null
    const context = getContextForSubtree(parentComponent);
    if (container.context === null) {
      container.context = context;
    } else {
      container.pendingContext = context;
    }

    return scheduleRootUpdate(current, element, expirationTime, callback);
  }

  function scheduleRootUpdate(
    current: Fiber,
    element: ReactNodeList,
    expirationTime: ExpirationTime,
    callback: ?Function,
  ) {
    if (__DEV__) {
      ...
    }
    // update对象是用来标记React应用中需要更新的节点
    const update = createUpdate(expirationTime);
    // Caution: React DevTools currently depends on this property
    // being called "element".
    // 设置update的相关属性
    update.payload = {element};

    callback = callback === undefined ? null : callback;
    if (callback !== null) {
      warningWithoutStack(
        typeof callback === 'function',
        'render(...): Expected the last optional `callback` argument to be a ' +
          'function. Instead received: %s.',
        callback,
      );
      update.callback = callback;
    }
    // 把update加入到 fiber 的updateQueue 里,一个节点上可能会产生很多次更新,需要 batchUpdates (批量更新)
    enqueueUpdate(current, update);

    // 有更新产生,告诉 React 开始调度
    scheduleWork(current, expirationTime);
    return expirationTime;
  }

ReactDOM.render 总结

  • 初次渲染 传入 APP 组件和 getElementById(root) 执行 ReactDOM.render 返回并执行 legacyRenderSubtreeIntoContainer

    • legacyRenderSubtreeIntoContainer 方法调用 legacyCreateRootFromDOMContainer 方法把 getElementById(root) 里的子节点清空,并把返回值 new ReactRoot 挂载到 getElementById(root) 节点的 _reactRootContainer 属性上

    • ReactRoot 生成实例时调用 react-reconcile 模块的 createContainer 传入 getElementById(root) 执行 createFiberRoot 生成一个 FiberRoot 对象挂载到实例的 _internalRoot

    • legacyRenderSubtreeIntoContainer 方法里 root.render 方法实际是调用 ReactRoot 原型上的render方法

    • ReactRoot.prototype.render 把子节点和实例生成的 _internalRoot FiberRoot 对象传入 react-reconcile 模块的 updateContainer 中

    • 在 updateContainer 中 react 计算出一个 expirationTime 传入 updateContainerAtExpirationTime 调用 scheduleRootUpdate 中做三件事

      • createUpddate 创建update对象来标记 react 需要更新的点

      • enqueueUpdate 将 update 加入到 RootFiber 的 updateQueue 中

      • scheduleWork 根据任务的优先级进行调度更新

下一篇

renderRoot

renderRoot

  • 调用 ReactDOM.render App 组件时会创建 FiberRoot -> createHostRootFiber,标记这个 fiber的 tag 为 HostRoot,创建 FiberRoot 的同时创建 RootFiber,对应 ReactDOM.render 方法的第二个参数对应的 Dom 节点
     function createFiberRoot(
       containerInfo: any,
       isConcurrent: boolean,
       hydrate: boolean,
     ): FiberRoot {
       // Cyclic construction. This cheats the type system right now because
       // stateNode is any.
       const uninitializedFiber = createHostRootFiber(isConcurrent);
       // ....
     }
    
     function createHostRootFiber(isConcurrent: boolean): Fiber {
       let mode = isConcurrent ? ConcurrentMode | StrictMode : NoContext;
    
       if (enableProfilerTimer && isDevToolsPresent) {
         // Always collect profile timings when DevTools are present.
         // This enables DevTools to start capturing timing at any point–
         // Without some nodes in the tree having empty base times.
         mode |= ProfileMode;
       }
    
       return createFiber(HostRoot, null, null, mode);
     }
  • 第一次执行到 beginWork 时首次更新的是 HostRoot,在 updateHostRoot 中会调用 reconcileChildFibers 调和子节点,在reconcileSingleElement -> createFiberFromElement -> createFiberFromTypeAndProps依次将 children element 创建成对应的 Fiber 对象, 默认 fiberTagIndeterminateComponent,根据 React.createElement 返回的 typefiberTag 标记为对应的组件类型,将创建完的 children 返回到 workLoop,遍历执行 performUnitOfWork -> beginWork, 根据这个 childrenfiberTag 对应依次更新,如此循环先创建,再更新。
  • 调用 workLoop 进行循环单元更新, 对整棵 fiberTree 都遍历一遍
  • 更新时捕获错误并进行处理
  • 更新流程结束后的处理
  • nextUnitOfWork 是每个节点自己更新完之后返回的第一个子节点
  • nextUnitOfWork 首次赋值为 createWorkInProgress 拷贝的一份 fiber 节点,以后的操作都是修改的 nextUnitOfWork, 防止改变当前 fiberTree
function renderRoot(
  root: FiberRoot,
  isYieldy: boolean,
  isExpired: boolean,
): void {
  isWorking = true;
  ReactCurrentOwner.currentDispatcher = Dispatcher;

  const expirationTime = root.nextExpirationTimeToWorkOn;

  // Check if we're starting from a fresh stack, or if we're resuming from
  // previously yielded work.
  // 说明将要执行的任务 root 和 expirationTime 和 nextRenderExpirationTime、nextRoot 预期的不一样, 可能是之前任务被高优先级的任务打断了。
  if (
    expirationTime !== nextRenderExpirationTime ||
    root !== nextRoot ||
    nextUnitOfWork === null //  更新结束 fiber 的 child,下一个节点, 首次为 null
  ) {
    // Reset the stack and start working from the root.
    // 重置
    resetStack();
    nextRoot = root;
    // root.nextExpirationTimeToWorkOn;
    nextRenderExpirationTime = expirationTime;
    // 将 fiber 拷贝成 workInProgress 对象操作,不直接操作 fiber 节点
    nextUnitOfWork = createWorkInProgress(
      nextRoot.current,
      null,
      nextRenderExpirationTime,
    );
    root.pendingCommitExpirationTime = NoWork;


  let didFatal = false;

  startWorkLoopTimer(nextUnitOfWork);
  // 开始循环 workLoop
  do {
    try {
      // 同步或过期任务则 isYieldy 为 false 表示不可中断
      workLoop(isYieldy);
    } catch (thrownValue) {
      // ... catch 错误
      // 致命错误
      if (didFatal) { }

      // workLoop break 中断的错误
      if (nextUnitOfWork !== null) { }

      // 可处理的错误
      if (nextRenderDidError) { }
    }
    break; // 遇到了某种错误跳出
  } while (true);

  // ...
  const rootWorkInProgress = root.current.alternate;
  // Ready to commit.
  // 最后 commit root
  onComplete(root, rootWorkInProgress, expirationTime);
}

workLoop

  • 根据 isYieldy 不同执行 performUnitOfWork
function workLoop(isYieldy) {
  // 同步或过期任务 isYieldy 为false 不可中断
  if (!isYieldy) {
    // Flush work without yielding
    while (nextUnitOfWork !== null) {
      nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
    }
  } else {
    // Flush asynchronous work until the deadline runs out of time.
    // !shouldYield() 还有剩余时间用来更新
    while (nextUnitOfWork !== null && !shouldYield()) {
      nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
    }
  }
}

performUnitOfWork

  • next 变量赋值为 beginWork 的返回值,即更新完之后会返回它的下一个节点
function performUnitOfWork(workInProgress: Fiber): Fiber | null {
  const current = workInProgress.alternate;

  // See if beginning this work spawns more work.
  startWorkTimer(workInProgress);
  let next;
  if (enableProfilerTimer) {
    if (workInProgress.mode & ProfileMode) {
      startProfilerTimer(workInProgress);
    }
    // 执行对整个树每个节点进行更新的操作,赋值 next 为更新完后的下一个节点
    next = beginWork(current, workInProgress, nextRenderExpirationTime);
    // 将最新的 props 赋值给目前正在用的 props
    workInProgress.memoizedProps = workInProgress.pendingProps;

    if (workInProgress.mode & ProfileMode) {
      // Record the render duration assuming we didn't bailout (or error).
      stopProfilerTimerIfRunningAndRecordDelta(workInProgress, true);
    }
  } else {
    next = beginWork(current, workInProgress, nextRenderExpirationTime);
    workInProgress.memoizedProps = workInProgress.pendingProps;
  }

  // 更新到最后的节点,叶子节点
  if (next === null) {
    // If this doesn't spawn new work, complete the current work.
    // 往上遍历
    next = completeUnitOfWork(workInProgress);
  }

  ReactCurrentOwner.current = null;

  return next;
}

beginWork

  • 判断当前 fiber 树是否需要更新,不需要更新的则进行优化
  • 根据节点类型进行对应的更新
  • 更新后调和子节点
function beginWork(
  // RootFiber
  current: Fiber | null,
  // 与 nextUnitOfWork 对应,下一个将要更新的节点
  workInProgress: Fiber,
  // root.nextExpirationTimeToWorkOn 表示一次更新内最高优先级的更新
  renderExpirationTime: ExpirationTime,
): Fiber | null {
  // 节点自身的过期时间
  const updateExpirationTime = workInProgress.expirationTime;


  if (current !== null) {
    const oldProps = current.memoizedProps;
    const newProps = workInProgress.pendingProps;
    if (
      // 前后两次props 相同
      oldProps === newProps &&
      // context 相关
      !hasLegacyContextChanged() &&
      // 没有更新
      (updateExpirationTime === NoWork ||
      // 更新优先级没有当前更新优先级高
        updateExpirationTime > renderExpirationTime)
    ) {
      // This fiber does not have any pending work. Bailout without entering
      // the begin phase. There's still some bookkeeping we that needs to be done
      // in this optimized path, mostly pushing stuff onto the stack.
      switch (workInProgress.tag) {
        // ...优化的过程
      }
      // 跳过当前节点及其子节点的更新
      return bailoutOnAlreadyFinishedWork(
        current,
        workInProgress,
        renderExpirationTime,
      );
    }
  }

  // Before entering the begin phase, clear the expiration time.
  workInProgress.expirationTime = NoWork;
  // 节点是有更新的情况
  switch (workInProgress.tag) {
    // ...根据节点类型,执行对应组件类型的更新
  }
}

bailoutOnAlreadyFinishedWork

  • 判断 fiber 的子树是否需要更新,如果需要更新则会 clone 一份老的 child 到 workInProgress.child 返回到 performUnitOfWorknext 再返回到 workLoopnextUnitOfWork 循环更新子节点, 否则为return null
function bailoutOnAlreadyFinishedWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderExpirationTime: ExpirationTime,
): Fiber | null {
  cancelWorkTimer(workInProgress);

  if (current !== null) {
    // Reuse previous context list
    workInProgress.firstContextDependency = current.firstContextDependency;
  }

  // Check if the children have any pending work.
  // 子节点最高优先级的更新
  const childExpirationTime = workInProgress.childExpirationTime;
  if (
    // 没有更新
    childExpirationTime === NoWork ||
    // 子树的优先级低
    childExpirationTime > renderExpirationTime
  ) {
    // The children don't have any work either. We can skip them.
    // TODO: Once we add back resuming, we should check if the children are
    // a work-in-progress set. If so, we need to transfer their effects.
    // 直接跳过子树更新
    return null;
  } else {
    // This fiber doesn't have work, but its subtree does. Clone the child
    // fibers and continue.
    // 子树有更新的情况
    // 当前节点不需要更新,说明 child 没有更新,把老的 workInProgress.child 复制一份,并返回
    cloneChildFibers(current, workInProgress);
    return workInProgress.child;
  }
}

根据 fiber 的 tag 类型进行更新

  • 进入更新前把当前 fiberexpirationTIme 置为 Nowork
  • 读取 workInProgress.type 函数式组件为 function``,class组件为class` 构造函数,dom原生组件为div这种字符串
  • penddingProps,新的渲染产生的 props

updateHostRoot

  • 一个 React 应用中一般只有一个 hostRoot,对应的 FiberRootFiber 对象, 对应的 elementReactDOM.render 方法的第二个参数 <div id="root" /> , 也就是 FiberRoot。

  • 创建更新的过程,ReactDOM.render -> ReactRoot.prototype.render -> DOMRenderer.updateContainer -> updateContainerAtExpirationTime -> scheduleRootUpdate

function scheduleRootUpdate(
  current: Fiber, // RootFiber
  element: ReactNodeList, // ReactDOM.render 的第一个参数
  expirationTime: ExpirationTime,
  callback: ?Function,
) {

  // 创建 update,这个update tag 为 UpdateState
  const update = createUpdate(expirationTime);
  // Caution: React DevTools currently depends on this property
  // being called "element".
  // 这里的 payload 相当于 state ,跟 setState 调用效果相同
  update.payload = {element};

  callback = callback === undefined ? null : callback;
  if (callback !== null) {
    warningWithoutStack(
      typeof callback === 'function',
      'render(...): Expected the last optional `callback` argument to be a ' +
        'function. Instead received: %s.',
      callback,
    );
    update.callback = callback;
  }
  // 为 RootFiber 添加 updateQueue
  enqueueUpdate(current, update);

  scheduleWork(current, expirationTime);
  return expirationTime;
}
  • 更新过程, state 里只有一个属性,即上面方法的 payload,通过 processUpdateQueue 得到一个 element 属性新 state ,从这个 element 开始创建和更新整个 fiber tree 。
function updateHostRoot(current, workInProgress, renderExpirationTime) {
  pushHostRootContext(workInProgress);
  const updateQueue = workInProgress.updateQueue;
  invariant(
    updateQueue !== null,
    'If the root does not have an updateQueue, we should have already ' +
      'bailed out. This error is likely caused by a bug in React. Please ' +
      'file an issue.',
  );
  const nextProps = workInProgress.pendingProps;
  const prevState = workInProgress.memoizedState;
  // 首次 prevState 为null
  const prevChildren = prevState !== null ? prevState.element : null;
  // 执行 processUpdateQueue 将 updateQueue 执行得到有个 element 属性的新的state, 
  processUpdateQueue(
    workInProgress,
    updateQueue,
    nextProps,
    null,
    renderExpirationTime,
  );
  // 拿到 scheduleRootUpdate 时的 update.payload 
  const nextState = workInProgress.memoizedState;
  // Caution: React DevTools currently depends on this property
  // being called "element".
  // 拿到 element 即 <App />
  const nextChildren = nextState.element;
  // 如果相同,则跳过更新,
  // 一般不会在 RootFiber 上创建更新,而是在 ReactDOM.render 传入的第一个参数那个节点(app)上创建更新
  if (nextChildren === prevChildren) {
    // If the state is the same as before, that's a bailout because we had
    // no work that expires at this time.
    // 服务端渲染相关,复用节点
    resetHydrationState();
    return bailoutOnAlreadyFinishedWork(
      current,
      workInProgress,
      renderExpirationTime,
    );
  }
  const root: FiberRoot = workInProgress.stateNode;
  // 服务端渲染相关
  if (
    (current === null || current.child === null) &&
    root.hydrate &&
    enterHydrationState(workInProgress)
  ) {

    workInProgress.effectTag |= Placement;
    // 认为这是第一次渲染
    workInProgress.child = mountChildFibers(
      workInProgress,
      null,
      nextChildren,
      renderExpirationTime,
    );
  } else {
    // Otherwise reset hydration state in case we aborted and resumed another
    // root.
    // ReactDOM.render -> 首次渲染时,current 和 workInProgress 都是存在的, 那么会走 reconcileChildFibers 更新调和
    reconcileChildren(
      current,
      workInProgress,
      nextChildren,
      renderExpirationTime,
    );
    resetHydrationState();
  }
  return workInProgress.child;
}

setState和batchUpdates、scheduleWork

setState 和 batchUpdates

setState 和 forceUpdate

  • 给节点的 Fiber 创建更新
  • 更新的类型不同

来源

Component 组件原型上的方法,他们调用的 enqueueSetStateenqueueForceUpdate 都是来自不同平台创建的 updater 对象上的,运行在浏览器中的 updater 对象来自 ReactFiberClassComponent.js 里的 classComponentUpdater 对象上。

// ReactBaseClasses.js

function Component(props, context, updater) {
  this.props = props;
  this.context = context;
  // If a component has string refs, we will assign a different object later.
  this.refs = emptyObject;
  // We initialize the default updater but the real one gets injected by the
  // renderer.
  // 作为参数让不同的平台来控制属性更新的方式
  this.updater = updater || ReactNoopUpdateQueue;
}

Component.prototype.setState = function(partialState, callback) {
  // 仅仅调用了 updater 上的方法 updater 是初始化的第三个参数的实例属性,跟平台相关
  // 这里的 this 即对应组件(fiber 节点)
  this.updater.enqueueSetState(this, partialState, callback, 'setState');
};
Component.prototype.forceUpdate = function(callback) {
  this.updater.enqueueForceUpdate(this, callback, 'forceUpdate');
};

classComponentUpdater

  • beginWork -> updateClassComponentconstructClassInstance 创建组件 instance 时将 classComponentUpdater 挂载到 instance.updater

  • enqueueSetState, enqueueForceUpdate 几乎是相同的, 其中 enqueueForceUpdate 只是将 update 对象的 tag 设置成了 ForceUpdate

  • enqueueSetState 方法的执行的操作和 updateContainer 几乎一模一样

const classComponentUpdater = {
  isMounted,
  enqueueSetState(inst, payload, callback) {
    // 从instanceMap中获取Fiber对象, inst 为调用setState的this,即那个组件
    const fiber = ReactInstanceMap.get(inst);
    // 创建当前时间
    const currentTime = requestCurrentTime();
    // 计算优先级时间
    const expirationTime = computeExpirationForFiber(currentTime, fiber);
    // 创建update对象
    const update = createUpdate(expirationTime);
    // 设置payload
    update.payload = payload;
    if (callback !== undefined && callback !== null) {
      if (__DEV__) {
        warnOnInvalidCallback(callback, 'setState');
      }
      // 设置callback
      update.callback = callback;
    }
    // 将 update 对象加入 fiber 的 updateQueue 中
    enqueueUpdate(fiber, update);
    // 执行调度更新
    scheduleWork(fiber, expirationTime);
  },
  enqueueReplaceState(inst, payload, callback) {
    // ...
  },
  enqueueForceUpdate(inst, callback) {
    // ...
  },
};

scheduleWork 开始调度

核心功能

  • scheduleWork 调用的地方有:ReactDOM.render、setState、forceUpdate、updateSuspenseComponent 等
  • 找到更新对应的 FiberRoot 节点,组件内 setState 时传入的都是组件的 Fiber 节点,不是绑定事件的元素对应的 fiber 节点
  • 符合条件时 -> 重置 stack,更新公共变量 -> 请求调度
  • 执行操作时的 Fiber 对象:
    • 点击组件上的元素
    • 调用setState
    • 找到 RootFiber 并把 RootFiber 加入到调度中,而不是执行setState的Fiber节点加入调度队列,更新的开始也是从RootFiber开始
  • Fiber 结构调度

scheduleWork

scheduleWork

每一次进入调度队列的只有 FiberRoot 对象, 更新也是从 FiberRoot 对象上开始的。

function scheduleWork(fiber: Fiber, expirationTime: ExpirationTime) {
  // 找到 FiberRoot,并更新 FiberTree 上所有 Fiber 节点的expirationTime
  const root = scheduleWorkToRoot(fiber, expirationTime);
  // 没有 FiberRoot 返回
  if (root === null) {
    return;
  }

  if (
    !isWorking && // 没有执行渲染, isWorking 表示有任务正在进行中
    nextRenderExpirationTime !== NoWork && // 任务是个异步的,执行到一半了,把执行权交还给浏览器执行
    expirationTime < nextRenderExpirationTime // 新的任务优先级高于现在的任务
  ) {
    // 新的高优先级的任务打断了老的低优先级任务,优先执行高优先级的任务
    // This is an interruption. (Used for performance tracking.)
    // 用来记录
    interruptedBy = fiber;
    // 重置并回退之前更新过的state
    resetStack();
  }
  markPendingPriorityLevel(root, expirationTime);
  if (
    // If we're in the render phase, we don't need to schedule this root
    // for an update, because we'll do it before we exit...
    // 没有正在工作, isWorking 和 isCommitting 对应两个不同阶段,isWorking包含isCommitting 和 rendering, 简单说就是要么处于没有 work 的状态,要么只能在 commit 阶段
    !isWorking ||
    // 正在提交,也就是更新dom 树的渲染阶段
    isCommitting ||
    // ...unless this is a different root than the one we're rendering.
    // 不同的 root,一般不存在不同
    nextRoot !== root
  ) {
    // 调用了 markPendingPriorityLevel 后 rootExpirationTime 不一定是传入进来的 expirationTime
    const rootExpirationTime = root.expirationTime;
    // 开始调度
    requestWork(root, rootExpirationTime);
  }
  // componentWillUpdate、componentDidUpdate 函数里调用 setState,死循环。。
  if (nestedUpdateCount > NESTED_UPDATE_LIMIT) {
    // Reset this back to zero so subsequent updates don't throw.
    nestedUpdateCount = 0;
    invariant(
      false,
      'Maximum update depth exceeded. This can happen when a ' +
        'component repeatedly calls setState inside ' +
        'componentWillUpdate or componentDidUpdate. React limits ' +
        'the number of nested updates to prevent infinite loops.',
    );
  }
}

scheduleWorkToRoot

  • 根据传入的 Fiber 对象向上寻找 RootFiber对象
  • 同时更新所有子树上的 expirationTime
  • 更新父节点上的 childExpirationTime
function scheduleWorkToRoot(fiber: Fiber, expirationTime): FiberRoot | null {
  // Update the source fiber's expiration time
  if (
    // 没有更新操作
    fiber.expirationTime === NoWork ||
    // 之前有更新产生且没完成,但当前fiber更新的优先级低于当前的更新
    fiber.expirationTime > expirationTime
  ) {
    // 设置成高优先级的 expirationTime
    fiber.expirationTime = expirationTime;
  }
  // 同理更新  alternate 的expirationTime
  let alternate = fiber.alternate;
  if (
    alternate !== null &&
    (alternate.expirationTime === NoWork ||
      alternate.expirationTime > expirationTime)
  ) {
    alternate.expirationTime = expirationTime;
  }
  // Walk the parent path to the root and update the child expiration time.
  // return 即父节点
  let node = fiber.return;
  let root = null;
  // RootFiber 的 return 才为null
  if (node === null && fiber.tag === HostRoot) {
    // 返回 FiberRoot
    root = fiber.stateNode;
  } else {
    // 循环查找 RootFiber 对象
    while (node !== null) {
      alternate = node.alternate;
      // childExpirationTime 是Fiber子树当中优先级最高的 expirationTime
      // 如果符合条件,将childExpirationTime设置成更高优先级的更新
      if (
        node.childExpirationTime === NoWork ||
        node.childExpirationTime > expirationTime
      ) {
        node.childExpirationTime = expirationTime;
        if (
          alternate !== null &&
          (alternate.childExpirationTime === NoWork ||
            alternate.childExpirationTime > expirationTime)
        ) {
          alternate.childExpirationTime = expirationTime;
        }
      } else if (
        alternate !== null &&
        (alternate.childExpirationTime === NoWork ||
          alternate.childExpirationTime > expirationTime)
      ) {
        alternate.childExpirationTime = expirationTime;
      }
      // 找到 RootFiber 结束循环
      if (node.return === null && node.tag === HostRoot) {
        root = node.stateNode;
        break;
      }
      // 继续循环
      node = node.return;
    }
  }
  return root;
}

resetStack

function resetStack() {
  // nextUnitOfWork 是用来记录下一个要更新的节点
  if (nextUnitOfWork !== null) {
    // 向上找被打断的任务
    let interruptedWork = nextUnitOfWork.return;
    while (interruptedWork !== null) {
      // 退回状态,因为更高优先级的任务也是从头更新的,避免更新过的组件 state 错乱
      unwindInterruptedWork(interruptedWork);
      interruptedWork = interruptedWork.return;
    }
  }

  // 重置初始值,进行新的优先级任务更新
  nextRoot = null;
  nextRenderExpirationTime = NoWork;
  nextLatestAbsoluteTimeoutMs = -1;
  nextRenderDidError = false;
  nextUnitOfWork = null;
}

requestWork

  • !isWorking && isCommitting 正在提交也就是更新dom树的渲染阶段执行
  • 将 FiberRoot 节点加入到 root 调度队列中
  • 判断是否批量更新
  • 根据 expirationTime 的类型判断调度的类型
function requestWork(root: FiberRoot, expirationTime: ExpirationTime) {
  // 将当前 FiberRoot 优先级设置为最高
  addRootToSchedule(root, expirationTime);
  // isRendering表示调度已经执行了,循环开始了,render 阶段不需要重新 requestWork 了
  if (isRendering) {
    // Prevent reentrancy. Remaining work will be scheduled at the end of
    // the currently rendering batch.
    return;
  }
  // 批量处理相关
  // 调用 setState 时在 enqueueUpdates 前 batchedUpdates 会把 isBatchingUpdates 设置成 true, 和事件系统有关
  // 为 true 则为调用 performSyncWork,则没有调度更新
  if (isBatchingUpdates) {
    // Flush work at the end of the batch.
    if (isUnbatchingUpdates) {
      // ...unless we're inside unbatchedUpdates, in which case we should
      // flush it now.
      nextFlushedRoot = root;
      nextFlushedExpirationTime = Sync;
      performWorkOnRoot(root, Sync, true);
    }
    return;
  }

  // TODO: Get rid of Sync and use current time?
  // 同步更新
  if (expirationTime === Sync) {
    performSyncWork();
  } else {
  // 异步调度,独立的 react 模块包,利用浏览器有空闲的时候进行执行,设置 deadline 在此之前执行
    scheduleCallbackWithExpirationTime(root, expirationTime);
  }
}

addRootToSchedule

  • 判断当前 FiberRoot 是否调度过,单个或多个 FiberRoot 构建成单向链表结构
  • 如果调度过,设置当前任务优先级为最高优先级
function addRootToSchedule(root: FiberRoot, expirationTime: ExpirationTime) {
  // Add the root to the schedule.
  // Check if this root is already part of the schedule.
  // 首次渲染 root 没有进入调度
  if (root.nextScheduledRoot === null) {
    // This root is not already scheduled. Add it.
    root.expirationTime = expirationTime;
    // 只有一个 root 的情况
    if (lastScheduledRoot === null) {
      firstScheduledRoot = lastScheduledRoot = root;
      root.nextScheduledRoot = root;
    } else {
      // 多个 root 构建单链表结构
      lastScheduledRoot.nextScheduledRoot = root;
      lastScheduledRoot = root;
      lastScheduledRoot.nextScheduledRoot = firstScheduledRoot;
    }
  } else {
    // This root is already scheduled, but its priority may have increased.
    // 传入的 FiberRoot 已经进入过调度
    const remainingExpirationTime = root.expirationTime;
    if (
      remainingExpirationTime === NoWork ||
      // root的 expirationTime 优先级低
      expirationTime < remainingExpirationTime
    ) {
      // Update the priority.
      // 把当前 root 的优先级设置为当前优先级最高的
      root.expirationTime = expirationTime;
    }
  }
}

batchUpdates 批量更新

  • React 每次创建更新都会执行 requestWork,例如 setState
  • 在 requestWrok 中根据全局变量 isBatchingUpdates 判断是否批量更新,根据 expirationTime 类型判断是同步执行,还是异步调度

setState 调用

demo

import React from 'react'
import { unstable_batchedUpdates as batchedUpdates } from 'react-dom'

export default class BatchedDemo extends React.Component {
  state = {
    number: 0,
  }

  componentDidMount () {
    const btn = document.getElementById('btn');
    // 方法四, 结果输出 1, 2, 3
    btn.addEventListener('click', this.handleClick);
  }


  handleClick = () => {
    // 方法一, 结果输出 0, 0, 0
    // 事件处理函数自带`batchedUpdates`
    this.countNumber()


    // 方法二, 结果输出 1, 2, 3
    // setTimeout中没有`batchedUpdates`
    // setTimeout(() => {
    //   this.countNumber()
    // }, 0)

    // 方法三, 结果输出 0, 0, 0
    // 主动`batchedUpdates`
    // setTimeout(() => {
    //   batchedUpdates(() => this.countNumber())
    // }, 0)

    /**
     * setState为函数式调用,记录在firstUpdate的单链表结构里的payload为 function
     */
  }

  countNumber() {
    const num = this.state.number
    this.setState({
      number: num + 1,
    });
    console.log(this.state.number)
    this.setState(() => {
      return {
        number: num + 2,
      };
    });
    console.log(this.state.number)
    this.setState({
      number: num + 3,
    })
    console.log(this.state.number)
  }

  render() {
    // React 自定义事件调用
    return <button onClick={this.handleClick} >Num: {this.state.number}</button>
    // addEventListener 原生事件调用
    return <button id="btn" >Num: {this.state.number}</button>
  }
}

涉及到更改 isBatchingUpdates 变量的两个主要函数, events模块都引用了这两个函数

  • interactiveUpdates
function dispatchInteractiveEvent(topLevelType, nativeEvent) {
  interactiveUpdates(dispatchEvent, topLevelType, nativeEvent);
}

function dispatchEvent(topLevelType, nativeEvent) {
  if (!_enabled) {
    return;
  }

  var nativeEventTarget = getEventTarget(nativeEvent);
  var targetInst = getClosestInstanceFromNode(nativeEventTarget);
  if (targetInst !== null && typeof targetInst.tag === 'number' && !isFiberMounted(targetInst)) {
    // If we get an event (ex: img onload) before committing that
    // component's mount, ignore it for now (that is, treat it as if it was an
    // event on a non-React tree). We might also consider queueing events and
    // dispatching them after the mount.
    targetInst = null;
  }

  var bookKeeping = getTopLevelCallbackBookKeeping(topLevelType, nativeEvent, targetInst);

  try {
    // Event queue being processed in the same cycle allows
    // `preventDefault`.
    batchedUpdates(handleTopLevel, bookKeeping);
  } finally {
    releaseTopLevelCallbackBookKeeping(bookKeeping);
  }
}

function interactiveUpdates<A, B, R>(fn: (A, B) => R, a: A, b: B): R {
  if (isBatchingInteractiveUpdates) {
    return fn(a, b);
  }
  // If there are any pending interactive updates, synchronously flush them.
  // This needs to happen before we read any handlers, because the effect of
  // the previous event may influence which handlers are called during
  // this event.
  if (
    !isBatchingUpdates &&
    !isRendering &&
    lowestPriorityPendingInteractiveExpirationTime !== NoWork
  ) {
    // Synchronously flush pending interactive updates.
    performWork(lowestPriorityPendingInteractiveExpirationTime, null);
    lowestPriorityPendingInteractiveExpirationTime = NoWork;
  }
  const previousIsBatchingInteractiveUpdates = isBatchingInteractiveUpdates;
  const previousIsBatchingUpdates = isBatchingUpdates;
  isBatchingInteractiveUpdates = true;
  isBatchingUpdates = true;
  try {
    // 这里的 fn 为 dispatchEvent 自定义事件函数
    return fn(a, b);
  } finally {
    isBatchingInteractiveUpdates = previousIsBatchingInteractiveUpdates;
    isBatchingUpdates = previousIsBatchingUpdates;
    if (!isBatchingUpdates && !isRendering) {
      performSyncWork();
    }
  }
}
  • batchedUpdates
function batchedUpdates<A, R>(fn: (a: A) => R, a: A): R {
  const previousIsBatchingUpdates = isBatchingUpdates;
  isBatchingUpdates = true;
  try {
    // 这里的 fn 为setState, 每次setState 都会进入 requestWork
    return fn(a);
  } finally {
    isBatchingUpdates = previousIsBatchingUpdates;
    if (!isBatchingUpdates && !isRendering) {
      performSyncWork();
    }
  }
}

方法一: 直接调用setState

  • React 的点击事件执行的是 React 包装过的 Event,执行栈:dispatchInteractiveEvent -> interactiveUpdates -> interactiveUpdates$1 这个函数里会将 isBatchingUpdates 置为 true, 并且 previousIsBatchingUpdates 记录首次加载的 isBatchingUpdates 的值(false), 执行 try 里的 fn,执行栈里推入 dispatchEvent -> batchedUpdates -> batchedUpdates$1 这个函数里也会将 isBatchingUpdates 置为true, 并且用previousIsBatchingUpdates 记录原 isBatchingUpdates 值(此时为 true );batchedUpdates$1 函数里try { fn } finally { ... }, 这里的 fn 就是setState了,执行栈直接推入 enqueueSetState -> scheduleWork -> requestWork 此时 isBatchingUpdates 为 true, 因此每次调用setState 都不会执行 performSyncWork(),最后finally 里 判断 !isBatchingUpdates && !isRendering 为false,不执行 performSyncWork(), 出栈到 interactiveUpdates$1 函数里的try {...} finally { ... } 重新将 isBatchingUpdates 赋值为false,判断 !isBatchingUpdates && !isRendering 为真(注意这个函数里的previousIsBatchingUpdates为false),因此执行 performSyncWork() 执行更新

  • 多次的 setState 在 enqueueUpdates 函数中,fiber 对象的 baseState 仍然是 0, 但是 fiber 对象上的 updateQueue 更新队列上已经记录好了多次 update 对象将要更新 state 的 payload。

  • 函数调用栈

未使用setTimeout

  • upDateQueue

updateQueue

方法二: 使用setTimeout, setTimeout(() => (this.setState(xxx))),或者 Promise.then(() => { this.setState({xxx})})

  • React 的点击事件执行的是 React 包装过的 Event,执行栈:dispatchInteractiveEvent -> interactiveUpdates -> interactiveUpdates$1 这个函数里会将 isBatchingUpdates 置为true, 并且 previousIsBatchingUpdates 记录首次加载的 isBatchingUpdates 的值(false), 执行 try 里的 fn,执行栈里推入 dispatchEvent -> batchedUpdates -> batchedUpdates$1 这个函数里也会将 isBatchingUpdates 置为 true, 并且 用previousIsBatchingUpdates 记录原 isBatchingUpdates 值(此时为 true );batchedUpdates$1 函数里 try { fn } finally { ... } 这里的 fn 就是setTimeout 为异步(到下一次事件循环才执行),判断 !isBatchingUpdates && !isRendering 为 false,不执行 performSyncWork, 出栈到 interactiveUpdates$1 函数里的 try {...} finally { ... } 重新将 isBatchingUpdates 赋值为false,判断 !isBatchingUpdates && !isRendering 为真(注意这个函数里的 previousIsBatchingUpdates 为 false),执行 performSyncWork() ,回到组件回调函数里,到这里才走完自定义事件的回调。

  • setTimeout 回调属于 marcoTask,它的执行在下一次任务队列开始,在执行 setTimeout 的回调,直接调用 setState,执行栈会推入 enqueueSetState -> scheduleWork -> requestWork,此时 isBatchingUpdates 为 false(等到setTimeout 执行时 isBatchingUpdates 赋值为 false 了), 因此每走一次 setState 都会走一次 performSyncWork() 将 state 更新。

  • 函数调用栈

使用setTimeout

  • updateQueue 里的 baseState 在每次执行完上一次 performWork 后都会更新为最新的 state

方法三: 在setTimeout里主动调用 batchUpdates

  • React 的点击事件执行的是 React 包装过的 Event,执行栈:dispatchInteractiveEvent -> interactiveUpdates -> interactiveUpdates$1 这个函数里会将 isBatchingUpdates 置为 true, 并且 previousIsBatchingUpdates 记录首次加载的 isBatchingUpdates的值(false), 执行 try 里的 fn,执行栈里推入 dispatchEvent -> batchedUpdates -> batchedUpdates$1 这个函数里也会将 isBatchingUpdates 置为true, 并且 用previousIsBatchingUpdates 记录原 isBatchingUpdates 值(此时为 true );batchedUpdates$1 函数里try { fn } finally { ... } 这里的 fn 就是setTimeout为异步(到下一次事件循环才执行),判断 !isBatchingUpdates && !isRendering 为false,不执行 performSyncWork, 出栈到 interactiveUpdates$1 函数里的try {...} finally { ... } 重新将 isBatchingUpdates 赋值为false,判断 !isBatchingUpdates && !isRendering 为真(注意这个函数里的 previousIsBatchingUpdates 为 false),因此执行 performSyncWork() ,回到组件回调函数里,到这里才走完自定义事件的回调。

  • 由于 setTimeout() 函数里又使用 batchedUpdates 函数主动包裹一次,因此执行栈里推入setTimeout的回调, 将 isBatchingUpdates 置为true, 并且 previousIsBatchingUpdates 记录的 原isBatchingUpdates的值(false), 执行 fn 即调用setState,执行栈会推入 enqueueSetState -> scheduleWork -> requestWork, 在requestWork里判断 isBatchingUpdates 为 true,直接 return 不执行 performSyncWork 函数,执行完三次 setState后,fiber节点上的 updateQueue 的baseState还是 0 ,fisrtUpdate为单链表结构记录着每次setState的payload ;执行完三次 setState 后回到 batchedUpdates$1 函数里try {...} finally { ... } 将 isBatchingUpdates 重新赋值为 false,再走performSyncWork() 将 lastUpdate 的 payload 更新到页面上

  • 函数调用栈

主动调用

  • upDateQueue 和方法一直接调用一样

方法四: 原生事件绑定

  • 原生事件未走自定义事件,因此没有走 dispatchInteractiveEvent -> interactiveUpdates 这个流程, isBatchingUpdates 始终为 false,因此每次setState 都会走一次 performSyncWork()

  • 函数调用栈

原生事件

总结 setState 是同步还是异步

  • setState 方法调用时本身是同步的,但调用 setState 不表示 state 是立即更新的, state 的更新是根据我们执行环境的上下文来判断的

  • 如果处于批量更新的情况下 state 就不是立即更新的,如果不处于批量更新情况下有可能立即更新

  • 有可能 是因为现在有 asyncMode 异步渲染的情况,state 也不是立即更新的,需要进入异步调度的过程。

上一篇
下一篇

completeUnitOfWork

回顾 performUnitOfWork

  • performUnitOfWork 遍历 fiber 树的顺序
    image
function performUnitOfWork(workInProgress: Fiber): Fiber | null {
  const current = workInProgress.alternate;

  // See if beginning this work spawns more work.
  startWorkTimer(workInProgress);
  let next;
  if (enableProfilerTimer) {
    if (workInProgress.mode & ProfileMode) {
      startProfilerTimer(workInProgress);
    }
    // 执行对整个树每个节点进行更新的操作,赋值 next 为更新完后的下一个节点
    next = beginWork(current, workInProgress, nextRenderExpirationTime);
    // 将最新的 props 赋值给目前正在用的 props
    workInProgress.memoizedProps = workInProgress.pendingProps;

    if (workInProgress.mode & ProfileMode) {
      // Record the render duration assuming we didn't bailout (or error).
      stopProfilerTimerIfRunningAndRecordDelta(workInProgress, true);
    }
  } else {
    next = beginWork(current, workInProgress, nextRenderExpirationTime);
    workInProgress.memoizedProps = workInProgress.pendingProps;
  }

  // 更新到最后的节点,叶子节点
  if (next === null) {
    // If this doesn't spawn new work, complete the current work.
    // 有兄弟节点返回兄弟节点,否则返回null,往上遍历
    next = completeUnitOfWork(workInProgress);
  }

  ReactCurrentOwner.current = null;

  return next;
}

completeUnitOfWork

  • 当一侧的子节点执行 beginWork 完成各个组件的更新,返回它的第一个 child,当返回 null 时,执行 completeUnitOfWork

  • beginWork 过程中, 在 update 某个节点时如果报错或者 throw promise,会被 catch 到,在 throwException 中给节点增加 Incompleteeffect;仍在 renderRoot 的循环中,节点报错,则不会继续渲染它的子节点,直接completeUnitOfWork ,如果 effectIncomplete,直接走 unwindWork 流程

  • completeWork 之后首先执行 resetChildExpirationTime 来重置 childExpirationTime

  • completeWork 之后赋值的 firstEffectlastEffect 链,在 commit 阶段根据这个 effect 链对每个节点执行操作

  • unwindWork: 如果 effectshouldCapture 则将 shouldCapture 改成 DidCapture,并返回节点,返回的是能处理错误的那个节点,跳出 completeUnitOfWork 循环,再继续 beginWork,此时已经有一个错误的 updateupdateQueue 中了,在 processUpdateQueue 中会处理 firstCapturedUpdate,计算出有错误情况下的 state ;否则返回 null

    • finishClassComponent 中如果非首次渲染并且有 DidCapture 则会调用 forceUnmountCurrentAndReconcile,进行两次 reconcileChildFibers
      function forceUnmountCurrentAndReconcile(
        current: Fiber,
        workInProgress: Fiber,
        nextChildren: any,
        renderExpirationTime: ExpirationTime,
      ) {
        // This function is fork of reconcileChildren. It's used in cases where we
        // want to reconcile without matching against the existing set. This has the
        // effect of all current children being unmounted; even if the type and key
        // are the same, the old child is unmounted and a new child is created.
        //
        // To do this, we're going to go through the reconcile algorithm twice. In
        // the first pass, we schedule a deletion for all the current children by
        // passing null.
        // nextChildren 为 null, 强制把目前子树节点都删除,渲染出没有子树的 classComponent
        workInProgress.child = reconcileChildFibers(
          workInProgress,
          current.child,
          null, 
          renderExpirationTime,
        );
        // In the second pass, we mount the new children. The trick here is that we
        // pass null in place of where we usually pass the current child set. This has
        // the effect of remounting all children regardless of whether their their
        // identity matches.
        // 老的 children 为 null,清空老的,直接渲染新的 children ,跳过对比 key 的调和过程,提高渲染效率
        // nextChildren 已经是根据从错误的 update 里计算出来的state渲染的 children 了
        workInProgress.child = reconcileChildFibers(
          workInProgress,
          null, 
          nextChildren,
          renderExpirationTime,
        );
      }
  • 节点有错误,但不一定有错误处理能力,即在 throwException 中标记 shouldCapture 的节点可能是报错节点的父节点,因此 unwindWork 可能返回为 null

  • 判断是否有兄弟节点来执行不同的操作,如果有兄弟节点则直接返回兄弟节点,如果没有兄弟节点,循环找父节点的兄弟节点,直到 rootFiber

  • completeUnitOfWork 返回节点跳出循环的情况:

    • 返回兄弟节点
    • 子树中有报错,在父链中返回有错误处理能力的节点
function completeUnitOfWork(workInProgress: Fiber): Fiber | null {
  // Attempt to complete the current unit of work, then move to the
  // next sibling. If there are no more siblings, return to the
  // parent fiber.
  while (true) {
    // The current, flushed, state of this fiber is the alternate.
    // Ideally nothing should rely on this, but relying on it here
    // means that we don't need an additional field on the work in
    // progress.
    const current = workInProgress.alternate;
    // 父节点
    const returnFiber = workInProgress.return;
    // 兄弟节点
    const siblingFiber = workInProgress.sibling;

    // Incomplete 为这个节点出现错误并捕获的 effect
    // 逻辑与操作判断 workInProgress.effectTag 是否为 �Incomplete
    // �Incomplete & �Incomplete => �Incomplete
    // 非 �Incomplete & �Incomplete => NoEffect 即没错的情况
    if ((workInProgress.effectTag & Incomplete) === NoEffect) {
      // This fiber completed.
      if (enableProfilerTimer) {
        if (workInProgress.mode & ProfileMode) {
          startProfilerTimer(workInProgress);
        }

        nextUnitOfWork = completeWork(
          current,
          workInProgress,
          nextRenderExpirationTime,
        );

        if (workInProgress.mode & ProfileMode) {
          // Update render duration assuming we didn't error.
          stopProfilerTimerIfRunningAndRecordDelta(workInProgress, false);
        }
      } else {
        nextUnitOfWork = completeWork(
          current,
          workInProgress,
          nextRenderExpirationTime,
        );
      }
      // 更新都是从 root 节点开始,需要同步子树中更新优先级最高的 expirationTime 到 root 上
      // 完成一次节点更新后,需要更新 childExpirationTime
      // 重置 childExpirationTime (子节点当中更新优先级最高的对应的 expirationTime )
      stopWorkTimer(workInProgress);
      resetChildExpirationTime(workInProgress, nextRenderExpirationTime);

      if (
        returnFiber !== null &&
        // Do not append effects to parents if a sibling failed to complete
        // 没有错误才会更新, 有错误的不会渲染子节点的
        (returnFiber.effectTag & Incomplete) === NoEffect 
      ) {
        // Append all the effects of the subtree and this fiber onto the effect
        // list of the parent. The completion order of the children affects the
        // side-effect order.
        // 处理父节点的 effect
        // 真正要做的是把当前节点的 firstEffect 到 lastEffect (即当前节点的子节点的 effect ) 单向链表挂载到它的父节点的同样的位置

        // 表示 returnFiber 还未记录任何有副作用(effect)的子节点
        if (returnFiber.firstEffect === null) {
          // 当前节点的 firstEffect 赋值给 returnFiber
          returnFiber.firstEffect = workInProgress.firstEffect;
        }
        if (workInProgress.lastEffect !== null) {
          // 如果 returnFiber 已经有记录过别的 side effect 节点,当前节点的 firstEffect 则挂载在单向链表的最后
          if (returnFiber.lastEffect !== null) {
            returnFiber.lastEffect.nextEffect = workInProgress.firstEffect;
          }
          // 更新 lastEffect
          returnFiber.lastEffect = workInProgress.lastEffect;
        }

        // 处理当前节点的 effect
        // 当前节点也可能有 effect
        const effectTag = workInProgress.effectTag;
        // Skip both NoWork and PerformedWork tags when creating the effect list.
        // PerformedWork effect is read by React DevTools but shouldn't be committed.
        // 如果当前节点有 effect 且大于 1, PerformeWork 值为 1 给 devtool 用的,则将当前节点挂载到父节点的 side effect 链的最后一个
        if (effectTag > PerformedWork) {
          // 如果 returnFiber 有记录过别的 effect
          if (returnFiber.lastEffect !== null) {
            // 更新最后一个的 next
            returnFiber.lastEffect.nextEffect = workInProgress;
          } else {
            // 否则为第一个 effect
            returnFiber.firstEffect = workInProgress;
          }
          // 更新 lastEffect
          returnFiber.lastEffect = workInProgress;
        }
        // 一层一层往上,最终在 RootFiber 上赋值的 firstEffect 到 lastEffect 这个单向链表结构记录了整个应用的当中所有需要更新的 dom节点/组件对应的 fiber 对象 ,以最小程度更新应用
      }

      // 如果有兄弟节点则返回兄弟节点,跳出这个循环,继续 beginWork
      if (siblingFiber !== null) {
        // If there is more work to do in this returnFiber, do that next.
        return siblingFiber;
        // 如果没有兄弟节点,循环找父节点的兄弟节点
      } else if (returnFiber !== null) {
        // If there's no more work in this returnFiber. Complete the returnFiber.
        workInProgress = returnFiber;
        continue;
      } else {
        // 如果都没有,则到达 rootFiber 顶点了,更新阶段完成,进入 commitRoot 提交阶段
        // We've reached the root.
        return null;
      }
    } else {
      // Incomplete 抛出过错误的情况, workInProgress 为报错的那个组件, 并不一定是能处理错误的那个组件,可能不是 classComponent 或者 没有错误处理的能力
      // 可以处理错误的组件才会标记 shouldCapture,可能是父组件
      // 即 unwindWork 执行完后 next 可能为 null 的情况
      if (workInProgress.mode & ProfileMode) {
        // Record the render duration for the fiber that errored.
        stopProfilerTimerIfRunningAndRecordDelta(workInProgress, false);
      }

      // This fiber did not complete because something threw. Pop values off
      // the stack without entering the complete phase. If this is a boundary,
      // capture values if possible.
      // workInProgress 的 effect 如果为 shouldCapture ,那么返回 workInProgress ,并且将 shouldCapture 改成 DidCapture
      // 否则返回 null
      const next = unwindWork(workInProgress, nextRenderExpirationTime);
      // Because this fiber did not complete, don't reset its expiration time.
      if (workInProgress.effectTag & DidCapture) {
        // Restarting an error boundary
        stopFailedWorkTimer(workInProgress);
      } else {
        stopWorkTimer(workInProgress);
      }
      //  next 不为 null 即 workInProgress 是有 DidCapture 的 effectTag
      if (next !== null) {
        stopWorkTimer(workInProgress);

        if (enableProfilerTimer) {
          // Include the time spent working on failed children before continuing.
          if (next.mode & ProfileMode) {
            let actualDuration = next.actualDuration;
            let child = next.child;
            while (child !== null) {
              actualDuration += child.actualDuration;
              child = child.sibling;
            }
            next.actualDuration = actualDuration;
          }
        }

        // If completing this work spawned new work, do that next. We'll come
        // back here again.
        // Since we're restarting, remove anything that is not a host effect
        // from the effect tag.
        // 将 effectTag 变成只有 HostEffectMask 上所具有的 effect
        // shouldCapture、Incomplete 会被去掉
        next.effectTag &= HostEffectMask;
        // 返回的是能处理错误的那个节点,再继续 beginWork,此时会根据是否有 DidCapture 在调和子组件中做不同操作。
        return next;
      }

      if (returnFiber !== null) {
        // Mark the parent fiber as incomplete and clear its effect list.
        // 清空父链的 effect 链
        returnFiber.firstEffect = returnFiber.lastEffect = null;
         // 报错组件没有错误处理能力时,需要在父组件上增加 Incomplete,
        // 即子树中的组件如果报错,那对于父链上所有组件的 completeUnitOfWork 都会执行 unwindWork
        returnFiber.effectTag |= Incomplete;
      }

      if (siblingFiber !== null) {
        // If there is more work to do in this returnFiber, do that next.
        return siblingFiber;
      } else if (returnFiber !== null) {
        // If there's no more work in this returnFiber. Complete the returnFiber.
        workInProgress = returnFiber;
        continue;
      } else {
        return null;
      }
    }
  }

  return null;
}
  • ReactSideEffectTags
export const NoEffect = /*              */ 0b00000000000;
export const PerformedWork = /*         */ 0b00000000001;

// You can change the rest (and add more).
export const Placement = /*             */ 0b00000000010;
export const Update = /*                */ 0b00000000100;
export const PlacementAndUpdate = /*    */ 0b00000000110;
export const Deletion = /*              */ 0b00000001000;
export const ContentReset = /*          */ 0b00000010000;
export const Callback = /*              */ 0b00000100000;
export const DidCapture = /*            */ 0b00001000000;
export const Ref = /*                   */ 0b00010000000;
export const Snapshot = /*              */ 0b00100000000;

// Update & Callback & Ref & Snapshot
export const LifecycleEffectMask = /*   */ 0b00110100100;

// Union of all host effects
// HostEffectMask 为以上 effect 的并集,Incomplete、ShouldCapture 为 HostEffectMask 的补集
export const HostEffectMask = /*        */ 0b00111111111;

export const Incomplete = /*            */ 0b01000000000;
export const ShouldCapture = /*         */ 0b10000000000;

节点没有报错的情况

completeWork 完成节点更新

  • pop 出各种 context 相关的内容
  • 对于 HostComponent 执行初始化
  • 初始化监听事件
  • 仅对 HostComponent、HostText、SuspenseComponent 有操作

HostComponent 的更新

普通 dom 节点,如 div、span

核心功能

  • 如果不是首次渲染,会通过 diffProperties 计算需要更新的内容,以此进行对比来判断一个节点是否真的需要更新
  • 对比的是 dom property,不同的 property 处理方式不同

首次渲染

  • 首次渲染,会通过 createInstance 创建 dom ,并在 dom 对象上记录对应的 fiber 节点 和 props
  • 通过 appendAllChildren 构建 dom 树,从底往上只 append 一层 childchild.sibling
  • finalizeInitialChildren 初始化 dom 节点属性、初始化事件体系
  • dom 节点记录在 workInProgress.stateNode 属性上, 在 appendAllChildren 中读取
function completeWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderExpirationTime: ExpirationTime,
): Fiber | null {
  const newProps = workInProgress.pendingProps;

  // ...
  case HostComponent: {
    popHostContext(workInProgress);
    const rootContainerInstance = getRootHostContainer();
    const type = workInProgress.type;
    // current !== null => 非首次渲染
    // workInProgress.stateNode != null => 节点被创建过(挂载)
    if (current !== null && workInProgress.stateNode != null) {
      updateHostComponent(
        current,
        workInProgress,
        type,
        newProps,
        rootContainerInstance,
      );

      if (current.ref !== workInProgress.ref) {
        markRef(workInProgress);
      }
    } else {
      // 首次渲染
      if (!newProps) {
        invariant(
          workInProgress.stateNode !== null,
          'We must have new props for new mounts. This error is likely ' +
            'caused by a bug in React. Please file an issue.',
        );
        // This can happen when we abort work.
        break;
      }

      const currentHostContext = getHostContext();
      // 服务端渲染相关
      let wasHydrated = popHydrationState(workInProgress);
      if (wasHydrated) {

      } else {
        // 创建 dom instance
        let instance = createInstance(
          type,
          newProps,
          rootContainerInstance,
          currentHostContext,
          workInProgress,
        );
        
        // append 子节点
        appendAllChildren(instance, workInProgress, false, false);

        // Certain renderers require commit-time effects for initial mount.
        // (eg DOM renderer supports auto-focus for certain elements).
        // Make sure such renderers get scheduled for later work.
        if (
          // 初始化 react 事件体系,初始化 DOM 节点属性
          finalizeInitialChildren(
            instance,
            type,
            newProps,
            rootContainerInstance,
            currentHostContext,
          )
        ) {
          // 节点需要 autofocus 时才mark update
          markUpdate(workInProgress);
        }
        // 将 dom节点 记录在 stateNode 属性上, 在 appendAllChildren 中读取
        workInProgress.stateNode = instance;
      }

      if (workInProgress.ref !== null) {
        // If there is a ref on a host node we need to schedule a callback
        markRef(workInProgress);
      }
    }
    break;
  }
}

createInstance 创建 DOM

  • 根据 workInProgress.type 创建 dom
  • dom 对象上记录对应的 fiberprops
function createInstance(
  type: string,
  props: Props, // newProps
  rootContainerInstance: Container, // 
  hostContext: HostContext,
  internalInstanceHandle: Object, // workInProgress 
): Instance {
  let parentNamespace: string;
  if (__DEV__) {
  } else {
    parentNamespace = ((hostContext: any): HostContextProd);
  }
  // 创建 dom Element 
  const domElement: Instance = createElement(
    type,
    props,
    rootContainerInstance,
    parentNamespace,
  );
  // cache Fiber 将 fiber 对象存储在 domElemeent 的 internalInstanceKey 属性上
  precacheFiberNode(internalInstanceHandle, domElement);
  // cache props 将 newProps 存储在 domElement 的 internalEventHandlersKey 属性上,props 会有 dom 的 attr 相关的属性
  updateFiberProps(domElement, props);
  return domElement;
}

// createElement 创建 dom

function createElement(
  type: string,
  props: Object,
  rootContainerElement: Element | Document,
  parentNamespace: string,
): Element {
  let isCustomComponentTag;

  // Document 对象
  const ownerDocument: Document = getOwnerDocumentFromRootContainer(
    rootContainerElement,
  );
  let domElement: Element;
  let namespaceURI = parentNamespace;
  if (namespaceURI === HTML_NAMESPACE) {
    namespaceURI = getIntrinsicNamespace(type);
  }
  if (namespaceURI === HTML_NAMESPACE) {
    if (__DEV__) {}
    // 特殊节点处理,如果直接创建 script 标签,会自动执行里面的脚本
    // 通过先插入,后删除的方式获取 script 标签对应的 dom
    if (type === 'script') {
      // Create the script via .innerHTML so its "parser-inserted" flag is
      // set to true and it does not execute
      const div = ownerDocument.createElement('div');
      div.innerHTML = '<script><' + '/script>'; // eslint-disable-line
      // This is guaranteed to yield a script element.
      const firstChild = ((div.firstChild: any): HTMLScriptElement);
      domElement = div.removeChild(firstChild);
    } else if (typeof props.is === 'string') {
      domElement = ownerDocument.createElement(type, {is: props.is});
    } else {
      // 原生 dom 创建元素
      domElement = ownerDocument.createElement(type);
      // select 标签设置属性
      if (type === 'select' && props.multiple) {
        const node = ((domElement: any): HTMLSelectElement);
        node.multiple = true;
      }
    }
  } else {
    // 不是 html 的 nameSpace 
    domElement = ownerDocument.createElementNS(namespaceURI, type);
  }
  // 返回 domElement 
  return domElement;
}

appendAllChildren 构建 dom 树

  • 构建 dom 树,从底往上 append 子节点,只会 append 所有子树里第一层 dom/text 节点 及其对应的 sibling,不会 append child.child 中的 dom/text 节点
  • child 节点在执行 completeUnitOfWorkappend 自己的 childchild.sibling
appendAllChildren = function(
  parent: Instance, // 创建的 dom 
  workInProgress: Fiber,
  needsVisibilityToggle: boolean,
  isHidden: boolean,
) {
  // We only have the top Fiber that was created but we need recurse down its
  // children to find all the terminal nodes.
  // 当前节点的子节点
  let node = workInProgress.child;
  while (node !== null) {
    if (node.tag === HostComponent || node.tag === HostText) {
      // 子节点是 原生 dom 节点 或者纯文本节点
      // 直接调用原生 dom 的 appendChild
      appendInitialChild(parent, node.stateNode);
    } else if (node.tag === HostPortal) {
      // If we have a portal child, then we don't want to traverse
      // down its children. Instead, we'll get insertions from each child in
      // the portal directly.
    } else if (node.child !== null) {
      // 子节点不是 dom 标签或者纯文本节点, 且 child.child 不为null
      node.child.return = node;
      // 重新赋值 node 为 node.child,并跳过此次循环,进入下一次循环,一直往下找直到找到 dom 节点 或者 纯文本节点
      node = node.child;
      continue;
    }

    if (node === workInProgress) {
      return;
    }
    // 这个 while 循环目的是找到有兄弟节点的父节点,如果父节点有兄弟节点,则跳出这个小循环,会去遍历其兄弟节点
    // 如果没有兄弟节点,继续往上找最终会回到 node.return === workInProgress 跳出循环
    while (node.sibling === null) {
      if (node.return === null || node.return === workInProgress) {
        return;
      }
      node = node.return;
    }
    // 将兄弟节点的父节点都指向同一个
    node.sibling.return = node.return;
    // 有兄弟节点继续遍历
    node = node.sibling;
  }
};

finalizeInitialChildren 初始化 DOM 节点属性,初始化事件体系

  • 不同节点通过 trapBubbledEvent 绑定不同事件
  • setInitialDOMProperties 初始化 dom 节点属性、和事件监听
    • CSSPropertyOperations.setValueForStyles 初始化样式
    • DOMPropertyOperations.setValueForProperty 初始化 dom 属性,会对 nextProp 做一些判断,要忽略的属性直接 return,要删除的属性,将通过 node.removeAttribute 删除,否则通过 node.setAttribute 添加
function finalizeInitialChildren(
  domElement: Instance,
  type: string,
  props: Props,
  rootContainerInstance: Container,
  hostContext: HostContext,
): boolean {
  setInitialProperties(domElement, type, props, rootContainerInstance);
  // 节点需要 autofocus 时才 mark update
  return shouldAutoFocusHostComponent(type, props);
}

// setInitialProperties

function setInitialProperties(
  domElement: Element,
  tag: string,
  rawProps: Object,
  rootContainerElement: Element | Document,
): void {
  const isCustomComponentTag = isCustomComponent(tag, rawProps);
  if (__DEV__) {}

  // TODO: Make sure that we check isMounted before firing any of these events.
  // trapBubbledEvent 绑定不同事件
  let props: Object;
  switch (tag) {
    case 'iframe':
    case 'object':
      trapBubbledEvent(TOP_LOAD, domElement);
      props = rawProps;
      break;
    case 'video':
    case 'audio':
      // Create listener for each media event
      for (let i = 0; i < mediaEventTypes.length; i++) {
        trapBubbledEvent(mediaEventTypes[i], domElement);
      }
      props = rawProps;
      break;
    case 'source':
      trapBubbledEvent(TOP_ERROR, domElement);
      props = rawProps;
      break;
    case 'img':
    case 'image':
    case 'link':
      trapBubbledEvent(TOP_ERROR, domElement);
      trapBubbledEvent(TOP_LOAD, domElement);
      props = rawProps;
      break;
    case 'form':
      trapBubbledEvent(TOP_RESET, domElement);
      trapBubbledEvent(TOP_SUBMIT, domElement);
      props = rawProps;
      break;
    case 'details':
      trapBubbledEvent(TOP_TOGGLE, domElement);
      props = rawProps;
      break;
      // input option select textarea 为可交互输入的组件
    case 'input':
      ReactDOMInput.initWrapperState(domElement, rawProps);
      props = ReactDOMInput.getHostProps(domElement, rawProps);
      trapBubbledEvent(TOP_INVALID, domElement);
      // For controlled components we always need to ensure we're listening
      // to onChange. Even if there is no listener.
      ensureListeningTo(rootContainerElement, 'onChange');
      break;
    case 'option':
      ReactDOMOption.validateProps(domElement, rawProps);
      props = ReactDOMOption.getHostProps(domElement, rawProps);
      break;
    case 'select':
      ReactDOMSelect.initWrapperState(domElement, rawProps);
      props = ReactDOMSelect.getHostProps(domElement, rawProps);
      trapBubbledEvent(TOP_INVALID, domElement);
      // For controlled components we always need to ensure we're listening
      // to onChange. Even if there is no listener.
      ensureListeningTo(rootContainerElement, 'onChange');
      break;
    case 'textarea':
      ReactDOMTextarea.initWrapperState(domElement, rawProps);
      props = ReactDOMTextarea.getHostProps(domElement, rawProps);
      trapBubbledEvent(TOP_INVALID, domElement);
      // For controlled components we always need to ensure we're listening
      // to onChange. Even if there is no listener.
      ensureListeningTo(rootContainerElement, 'onChange');
      break;
    default:
      props = rawProps;
  }

  // 常用 props 错误提醒
  assertValidProps(tag, props);

  // 初始化 DOM 节点属性
  setInitialDOMProperties(
    tag,
    domElement,
    rootContainerElement,
    props,
    isCustomComponentTag,
  );
  // ...
}

// setInitialDOMProperties 
function setInitialDOMProperties(
  tag: string,
  domElement: Element,
  rootContainerElement: Element | Document,
  nextProps: Object,
  isCustomComponentTag: boolean,
): void {
  for (const propKey in nextProps) {
    if (!nextProps.hasOwnProperty(propKey)) {
      continue;
    }
    const nextProp = nextProps[propKey];
    if (propKey === STYLE) {
    // style 属性
      if (__DEV__) {
        if (nextProp) {
          // Freeze the next style object so that we can assume it won't be
          // mutated. We have already warned for this in the past.
          Object.freeze(nextProp);
        }
      }
      // Relies on `updateStylesByID` not mutating `styleUpdates`.
      // 设置样式,对于 width 类属性,自动加 px
      CSSPropertyOperations.setValueForStyles(domElement, nextProp);
    } else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
      // 文本属性,直接用原生 node.innerHTML = html 设置
      const nextHtml = nextProp ? nextProp[HTML] : undefined;
      if (nextHtml != null) {
        setInnerHTML(domElement, nextHtml);
      }
    } else if (propKey === CHILDREN) {
      // 子节点,这里只会是文本或数字了,dom 节点或者组件已经在 appendAllChildren 操作完了
      if (typeof nextProp === 'string') {
        // Avoid setting initial textContent when the text is empty. In IE11 setting
        // textContent on a <textarea> will cause the placeholder to not
        // show within the <textarea> until it has been focused and blurred again.
        // https://github.com/facebook/react/issues/6731#issuecomment-254874553
        const canSetTextContent = tag !== 'textarea' || nextProp !== '';
        if (canSetTextContent) {
          setTextContent(domElement, nextProp);
        }
      } else if (typeof nextProp === 'number') {
        setTextContent(domElement, '' + nextProp);
      }
    } else if (
      propKey === SUPPRESS_CONTENT_EDITABLE_WARNING ||
      propKey === SUPPRESS_HYDRATION_WARNING
    ) {
      // Noop
    } else if (propKey === AUTOFOCUS) {
      // We polyfill it separately on the client during commit.
      // We could have excluded it in the property list instead of
      // adding a special case here, but then it wouldn't be emitted
      // on server rendering (but we *do* want to emit it in SSR).
    } else if (registrationNameModules.hasOwnProperty(propKey)) {
      // 事件相关
      if (nextProp != null) {
        if (__DEV__ && typeof nextProp !== 'function') {
          warnForInvalidEventListener(propKey, nextProp);
        }
        // 事件监听
        ensureListeningTo(rootContainerElement, propKey);
      }
    } else if (nextProp != null) {
      // 最终 node.setAttribute 设置属性,或者 removeAttribute 删除不必要的属性
      DOMPropertyOperations.setValueForProperty(
        domElement,
        propKey,
        nextProp,
        isCustomComponentTag,
      );
    }
  }
}

updateHostComponent 更新渲染

  • 非首次渲染更新,需要对新老 props 里对应 domproperty 的对比是否有变化来进行不同的操作
updateHostComponent = function(
    current: Fiber,
    workInProgress: Fiber,
    type: Type,
    newProps: Props,
    rootContainerInstance: Container,
  ) {
    // If we have an alternate, that means this is an update and we need to
    // schedule a side-effect to do the updates.
    const oldProps = current.memoizedProps;
    // 新老 props 相同,直接 return 
    if (oldProps === newProps) {
      return;
    }
    // 当前节点对应的 DOM 节点
    const instance: Instance = workInProgress.stateNode;
    const currentHostContext = getHostContext();
    // TODO: Experiencing an error where oldProps is null. Suggests a host
    // component is hitting the resume path. Figure out why. Possibly
    // related to `hidden`.
    // updatePayload 为调用 diffProperties 返回的结果
    const updatePayload = prepareUpdate(
      instance,
      type,
      oldProps,
      newProps,
      rootContainerInstance,
      currentHostContext,
    );
    // TODO: Type this specific to this type of component.
    workInProgress.updateQueue = (updatePayload: any);
    // If the update payload indicates that there is a change or if there
    // is a new ref we mark this as an update. All the work is done in commitWork.
    // 如果有 updatePayload 则要标记更新,对于 input,option, select, textarea 默认为空数组,即便是 props 没有更新的内容,仍旧需要标记 update 
    if (updatePayload) {
      markUpdate(workInProgress);
    }
  };

diffProperties

  • 生成 updatePayload 赋值给 workInProgress.updateQueue
  • updatePayload 存在,则标记 effectupdate
  • 老 props 循环,如果新 props 没有而老 props 有则要删除
  • 新 props 循环,如果新 props 有这个 key,且新老 props[key] 值不想等则将这个 key 标记更新
  • 最终形成的 updatePayload 结构为 [k1, v1, k2, v2, k3, v3]\
function diffProperties(
  domElement: Element,
  tag: string,
  lastRawProps: Object,
  nextRawProps: Object,
  rootContainerElement: Element | Document,
): null | Array<mixed> {
  if (__DEV__) {
    validatePropertiesInDevelopment(tag, nextRawProps);
  }

  let updatePayload: null | Array<any> = null;

  let lastProps: Object;
  let nextProps: Object;
  // 特殊标签
  switch (tag) {
    case 'input':
      lastProps = ReactDOMInput.getHostProps(domElement, lastRawProps);
      nextProps = ReactDOMInput.getHostProps(domElement, nextRawProps);
      updatePayload = [];
      break;
    case 'option':
      lastProps = ReactDOMOption.getHostProps(domElement, lastRawProps);
      nextProps = ReactDOMOption.getHostProps(domElement, nextRawProps);
      updatePayload = [];
      break;
    case 'select':
      lastProps = ReactDOMSelect.getHostProps(domElement, lastRawProps);
      nextProps = ReactDOMSelect.getHostProps(domElement, nextRawProps);
      updatePayload = [];
      break;
    case 'textarea':
      lastProps = ReactDOMTextarea.getHostProps(domElement, lastRawProps);
      nextProps = ReactDOMTextarea.getHostProps(domElement, nextRawProps);
      updatePayload = [];
      break;
    default:
      lastProps = lastRawProps;
      nextProps = nextRawProps;
      if (
        typeof lastProps.onClick !== 'function' &&
        typeof nextProps.onClick === 'function'
      ) {
        // TODO: This cast may not be sound for SVG, MathML or custom elements.
        trapClickOnNonInteractiveElement(((domElement: any): HTMLElement));
      }
      break;
  }

  // props 错误提醒
  assertValidProps(tag, nextProps);

  let propKey;
  let styleName;
  let styleUpdates = null;
  // 循环老的 props,如果新 props 没有而老 props 有则要删除(style属性:将value置为 ’‘,其他属性置为 null)这个属性
  for (propKey in lastProps) {
    if (
      // 新的有
      nextProps.hasOwnProperty(propKey) ||
      // 老的没有
      !lastProps.hasOwnProperty(propKey) ||
      // 老的为 null
      lastProps[propKey] == null
    ) {
      // 如果符合这个条件则跳过,否则是新的没有且老的有,那么就要删除
      continue;
    }
    // 不符合以上条件则是删除操作
    if (propKey === STYLE) {
      const lastStyle = lastProps[propKey];
      for (styleName in lastStyle) {
        if (lastStyle.hasOwnProperty(styleName)) {
          if (!styleUpdates) {
            styleUpdates = {};
          }
          // 设置删除老的 style props
          styleUpdates[styleName] = '';
        }
      }
    } else if (propKey === DANGEROUSLY_SET_INNER_HTML || propKey === CHILDREN) {
      // Noop. This is handled by the clear text mechanism.
    } else if (
      propKey === SUPPRESS_CONTENT_EDITABLE_WARNING ||
      propKey === SUPPRESS_HYDRATION_WARNING
    ) {
      // Noop
    } else if (propKey === AUTOFOCUS) {
      // Noop. It doesn't work on updates anyway.
    } else if (registrationNameModules.hasOwnProperty(propKey)) {
      // This is a special case. If any listener updates we need to ensure
      // that the "current" fiber pointer gets updated so we need a commit
      // to update this element.
      if (!updatePayload) {
        updatePayload = [];
      }
    } else {
      // For all other deleted properties we add it to the queue. We use
      // the whitelist in the commit phase instead.
      // 要删除的属性,则 value 都设置为 null
      (updatePayload = updatePayload || []).push(propKey, null);
    }
  }

  // 循环新的 props,如果新 props 有这个 key,且新老 props[key] 值不想等则将这个 key 标记更新( style属性则为直接设置 style[key] = newValue ),
  for (propKey in nextProps) {
    // newValue
    const nextProp = nextProps[propKey];
    // oldValue
    const lastProp = lastProps != null ? lastProps[propKey] : undefined;
    if (
      // 自身没有这个 key
      !nextProps.hasOwnProperty(propKey) ||
      // 新老相等
      nextProp === lastProp ||
      // 均为 null
      (nextProp == null && lastProp == null)
    ) {
      // 符合以上条件则跳过, 否则就要加入 updatePayload 中
      continue;
    }
    // 对 style 属性的操作
    if (propKey === STYLE) {
      if (__DEV__) {
        if (nextProp) {
          // Freeze the next style object so that we can assume it won't be
          // mutated. We have already warned for this in the past.
          Object.freeze(nextProp);
        }
      }
      if (lastProp) {
        // Unset styles on `lastProp` but not on `nextProp`.
        for (styleName in lastProp) {
          if (
            // style 在老的 props 中,但不在新的 props 中,则属性对应的 value 设置为 ‘’(对应删除)
            lastProp.hasOwnProperty(styleName) &&
            (!nextProp || !nextProp.hasOwnProperty(styleName))
          ) {
            if (!styleUpdates) {
              styleUpdates = {};
            }
            styleUpdates[styleName] = '';
          }
        }
        // Update styles that changed since `lastProp`.
        for (styleName in nextProp) {
          // 如果新props 有且新老prop 值不想等,赋值 styleUpdates[styleName] 为新的值
          if (
            nextProp.hasOwnProperty(styleName) &&
            lastProp[styleName] !== nextProp[styleName]
          ) {
            if (!styleUpdates) {
              styleUpdates = {};
            }
            styleUpdates[styleName] = nextProp[styleName];
          }
        }
      } else {
        // Relies on `updateStylesByID` not mutating `styleUpdates`.
        if (!styleUpdates) {
          if (!updatePayload) {
            updatePayload = [];
          }
          // 没有老 props 直接添加
          updatePayload.push(propKey, styleUpdates);
        }
        styleUpdates = nextProp;
      }
    } else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
      const nextHtml = nextProp ? nextProp[HTML] : undefined;
      const lastHtml = lastProp ? lastProp[HTML] : undefined;
      if (nextHtml != null) {
        if (lastHtml !== nextHtml) {
          (updatePayload = updatePayload || []).push(propKey, '' + nextHtml);
        }
      } else {
        // TODO: It might be too late to clear this if we have children
        // inserted already.
      }
    } else if (propKey === CHILDREN) {
      // 子节点,文本类型的,不相同直接 push 
      if (
        lastProp !== nextProp &&
        (typeof nextProp === 'string' || typeof nextProp === 'number')
      ) {
        (updatePayload = updatePayload || []).push(propKey, '' + nextProp);
      }
    } else if (
      propKey === SUPPRESS_CONTENT_EDITABLE_WARNING ||
      propKey === SUPPRESS_HYDRATION_WARNING
    ) {
      // Noop
    } else if (registrationNameModules.hasOwnProperty(propKey)) {
      if (nextProp != null) {
        // We eagerly listen to this even though we haven't committed yet.
        if (__DEV__ && typeof nextProp !== 'function') {
          warnForInvalidEventListener(propKey, nextProp);
        }
        // 事件相关
        ensureListeningTo(rootContainerElement, propKey);
      }
      if (!updatePayload && lastProp !== nextProp) {
        // This is a special case. If any listener updates we need to ensure
        // that the "current" props pointer gets updated so we need a commit
        // to update this element.
        updatePayload = [];
      }
    } else {
      // For any other property we always add it to the queue and then we
      // filter it out using the whitelist during the commit.
      (updatePayload = updatePayload || []).push(propKey, nextProp);
    }
  }
  if (styleUpdates) {
    (updatePayload = updatePayload || []).push(STYLE, styleUpdates);
  }
  // updatePayload 的格式为 [k1, v1, k2, v2, k3, v3]
  return updatePayload;
}

HostText 更新

case HostText: {
  let newText = newProps;
  // 更新渲染
  if (current && workInProgress.stateNode != null) {
    const oldText = current.memoizedProps;
    // If we have an alternate, that means this is an update and we need
    // to schedule a side-effect to do the updates.
    // 新老text不同则标记更新
    updateHostText(current, workInProgress, oldText, newText);
  } else {
    // 首次渲染 
    if (typeof newText !== 'string') {
      invariant(
        workInProgress.stateNode !== null,
        'We must have new props for new mounts. This error is likely ' +
          'caused by a bug in React. Please file an issue.',
      );
      // This can happen when we abort work.
    }
    const rootContainerInstance = getRootHostContainer();
    const currentHostContext = getHostContext();
    let wasHydrated = popHydrationState(workInProgress);
    if (wasHydrated) {
      if (prepareToHydrateHostTextInstance(workInProgress)) {
        markUpdate(workInProgress);
      }
    } else {
      // 创建 textInstance 
      workInProgress.stateNode = createTextInstance(
        newText,
        rootContainerInstance,
        currentHostContext,
        workInProgress,
      );
    }
  }
  break;
}

// 判断不相同,则标记 update 
updateHostText = function(
    current: Fiber,
    workInProgress: Fiber,
    oldText: string,
    newText: string,
  ) {
    // If the text differs, mark it as an update. All the work in done in commitWork.
    if (oldText !== newText) {
      markUpdate(workInProgress);
    }
  };

// createTextInstance 创建 textInstance
function createTextInstance(
  text: string,
  rootContainerInstance: Container,
  hostContext: HostContext,
  internalInstanceHandle: Object,
): TextInstance {
  if (__DEV__) {
    const hostContextDev = ((hostContext: any): HostContextDev);
    validateDOMNesting(null, text, hostContextDev.ancestorInfo);
  }
  // 调用 document.createTextNode
  const textNode: TextInstance = createTextNode(text, rootContainerInstance);
  // cache Fiber, 将 fiber 对象存储在 domElemeent 的 internalInstanceKey 属性上
  precacheFiberNode(internalInstanceHandle, textNode);
  return textNode;
}

resetChildExpirationTime 重置 resetChildExpirationTime

  • 完成当前节点更新后,需要重置 childExpirationTime
  • childExpirationTime 是用来记录某一个节点子树当中优先级最高对应的那个 expirationTime
  • 整个应用的更新都是从 root 节点开始,需要同步子树中更新优先级最高的 expirationTimeroot
  • completeWork 完成节点更新后执行,重置 childExpirationTime (子节点当中更新优先级最高的对应的 expirationTime )
  • 没法直接拿到当前节点的所有子树最高优先级的 exprationTime, 只能通过遍历所有的第一层的子节点的 expirationTime 以及每个子节点的 childExpirationTime, completeUnitOfWork 是由低往上更新的过程,每个节点都会对应这个操作,因此每个节点都会更新到优先级最高的 expirationTime。对比 appendChild 时的操作类似,都只会 appendChild 第一层子节点的 dom
function resetChildExpirationTime(
  workInProgress: Fiber,
  renderTime: ExpirationTime,
) {
  // renderTime !== Never => 当前任务执行更新
  // workInProgress.childExpirationTime === Never => 当前节点的子节点没有更新
  // 直接返回
  if (renderTime !== Never && workInProgress.childExpirationTime === Never) {
    // The children of this component are hidden. Don't bubble their
    // expiration times.
    return;
  }

  let newChildExpirationTime = NoWork;

  // Bubble up the earliest expiration time.
  if (enableProfilerTimer && workInProgress.mode & ProfileMode) {
    // ....
  } else {
    let child = workInProgress.child;
    while (child !== null) {
      // 子节点的 expirationTime
      const childUpdateExpirationTime = child.expirationTime;
      // 子节点的子树中更新优先级最高的那个 expirationTime
      const childChildExpirationTime = child.childExpirationTime;
      if (
        newChildExpirationTime === NoWork ||
        (childUpdateExpirationTime !== NoWork && // 找到不为 NoWork且优先级最高的子节点对应的 expirationTime
          childUpdateExpirationTime < newChildExpirationTime)
      ) {
        newChildExpirationTime = childUpdateExpirationTime;
      }
      if (
        newChildExpirationTime === NoWork ||
        (childChildExpirationTime !== NoWork && // 找到子节点子树中优先级不为 NoWork 且更新优先级最高的 expirationTime
          childChildExpirationTime < newChildExpirationTime)
      ) {
        newChildExpirationTime = childChildExpirationTime;
      }
      // 横向查找(有兄弟节点的情况才会要去找更新优先级最高的 expirationTime)
      child = child.sibling;
    }
  }
  // 更新当前节点的 childExpirationTime
  workInProgress.childExpirationTime = newChildExpirationTime;
}

节点有报错的情况

  • 给报错的节点增加 Incompleteeffect,在 completeUnitOfWork 时会判断存在 Incomplete 则调用 unwindWork
  • 给父链上具有 error boundary 的节点增加 DidCaptureeffect
  • 创建错误相关的更新
// renderRoot

do {
  try {
    workLoop(isYieldy);
  } catch (thrownValue) {
    // 错误处理核心点:
    // 给报错的节点增加 Incomplete 副作用,在 completeUnitOfWork 时会判断存在 Incomplete 则调用 unwindWork, 不存在则调用 completeWork
    // 给父链上具有 error boundary 的节点增加副作用
    // 创建错误相关的更新
    
    // 非正常流程,不应该为 null
    if (nextUnitOfWork === null) {
      // This is a fatal error.
      // 致命错误,直接中断渲染流程
      didFatal = true;
      // 设置 nextFlushedRoot.expirationTime = NoWork; 
      onUncaughtError(thrownValue);
    } else {
      if (__DEV__) {
        // Reset global debug state
        // We assume this is defined in DEV
        (resetCurrentlyProcessingQueue: any)();
      }

      const failedUnitOfWork: Fiber = nextUnitOfWork;
      if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
        replayUnitOfWork(failedUnitOfWork, thrownValue, isYieldy);
      }

      // TODO: we already know this isn't true in some cases.
      // At least this shows a nicer error message until we figure out the cause.
      // https://github.com/facebook/react/issues/12449#issuecomment-386727431
      invariant(
        nextUnitOfWork !== null,
        'Failed to replay rendering after an error. This ' +
          'is likely caused by a bug in React. Please file an issue ' +
          'with a reproducing case to help us find it.',
      );

      const sourceFiber: Fiber = nextUnitOfWork;
      let returnFiber = sourceFiber.return;
      // 在更新 rootFiber 时出现致命错误
      if (returnFiber === null) {
        // This is the root. The root could capture its own errors. However,
        // we don't know if it errors before or after we pushed the host
        // context. This information is needed to avoid a stack mismatch.
        // Because we're not sure, treat this as a fatal error. We could track
        // which phase it fails in, but doesn't seem worth it. At least
        // for now.
        didFatal = true;
        onUncaughtError(thrownValue);
      } else {
        // 常规错误处理, 给报错节点增加 Incomplete 的 effect
        throwException(
          root,
          returnFiber,
          sourceFiber,
          thrownValue,
          nextRenderExpirationTime,
        );
        // 节点报错,则不会继续渲染它的子节点,直接 completeUnitOfWork ,直接走 unwindWork 流程
        nextUnitOfWork = completeUnitOfWork(sourceFiber);
        continue;
      }
    }
  }
  break;
} while (true);

onUncaughtError

  • 致命错误,设置 nextFlushedRoot.expirationTime = NoWork 取消当前 root 的更新
onUncaughtError(error: mixed) {
  nextFlushedRoot.expirationTime = NoWork;
  if (!hasUnhandledError) {
    hasUnhandledError = true;
    unhandledError = error;
  }
}

throwException

  • 常规错误处理, 给报错节点增加 Incompleteeffect
  • 清空报错节点的 firstEffect、lastEffect
  • suspened 异步组件抛出的 promise
  • 循环往上找(找父组件)可以处理错误的 classComponent,如果没有,那么会在 hostRoot 上进行内置处理, root 节点肯定会标记 ShouldCapture
  • 能处理错误节点的 classComponent 都会标记 effectShouldCapture
  • 创建错误更新,将 firstCapturedUpdate、lastCapturedUpdate链 加入到 updateQueue
  • getDerivedStateFromError 是在 update.payload 函数里执行的
  • componentDidCatch 是在 update.callback 里执行的
function throwException(
  root: FiberRoot,
  returnFiber: Fiber,
  sourceFiber: Fiber,
  value: mixed, // 抛出来的错误 value
  renderExpirationTime: ExpirationTime,
) {
  // The source fiber did not complete.
  sourceFiber.effectTag |= Incomplete;
  // Its effect list is no longer valid.
  // 清空 effect 链
  sourceFiber.firstEffect = sourceFiber.lastEffect = null;

  // throw 的是 promise 的情况,suspense 组件
  if (
    value !== null &&
    typeof value === 'object' &&
    typeof value.then === 'function'
  ) { // ...}

  // We didn't find a boundary that could handle this type of exception. Start
  // over and traverse parent path again, this time treating the exception
  // as an error.
  // nextRenderDidError = true 标记错误变量
  renderDidError();
  // 处理过的错误信息, 类似 Error 对象,有错误产生的文件、方法、行数等信息
  value = createCapturedValue(value, sourceFiber);
  let workInProgress = returnFiber;
  // 如果组件没有 getDerivedStateFromError 或 componentDidCatch,则是没有错误处理功能的
  // 需要往上找找到第一个可以处理错误的 classComponent 来进行错误的 update 的创建,并加入到 updateQueue 中,在 commit 阶段进行调用
  // 如果找不到可以进行错误处理的 classComponent,那么会在 hostRoot 上进行处理,算是一个内置的错误处理的方式,也会创建对应的错误的 update 
  do {
    switch (workInProgress.tag) {
      case HostRoot: {
        const errorInfo = value;
        // 标记 effect 
        workInProgress.effectTag |= ShouldCapture;
        workInProgress.expirationTime = renderExpirationTime;
        // 类似 setState 的创建一个错误处理的 update,创建 firstCapturedUpdate、lastCapturedUpdate链 , 创建 root 节点的错误更新
        const update = createRootErrorUpdate(
          workInProgress,
          errorInfo,
          renderExpirationTime,
        );
        // 类似 enqueueUpdate 的推入 updateQueue
        enqueueCapturedUpdate(workInProgress, update);
        return;
      }
      case ClassComponent:
        // Capture and retry
        const errorInfo = value;
        const ctor = workInProgress.type;
        const instance = workInProgress.stateNode;
        
        if (
          // 没有 DidCapture 
          (workInProgress.effectTag & DidCapture) === NoEffect &&
          // 并且有 静态方法 getDerivedStateFromError 或者 实例的 componentDidCatch 错误处理函数
          (typeof ctor.getDerivedStateFromError === 'function' ||
            (instance !== null &&
              typeof instance.componentDidCatch === 'function' &&
              !isAlreadyFailedLegacyErrorBoundary(instance)))
        ) {
          // 标记 effect 为 ShouldCapture
          workInProgress.effectTag |= ShouldCapture;
          workInProgress.expirationTime = renderExpirationTime;
          // Schedule the error boundary to re-render using updated state
          // 类似 setState 的创建一个错误处理的 update, 创建 firstCapturedUpdate、lastCapturedUpdate链 
          const update = createClassErrorUpdate(
            workInProgress,
            errorInfo,
            renderExpirationTime,
          );
          // 加入到 updateQueue 中
          enqueueCapturedUpdate(workInProgress, update);
          return;
        }
        break;
      default:
        break;
    }
    // 往上遍历
    workInProgress = workInProgress.return;
  } while (workInProgress !== null);
}
  • createRootErrorUpdate 创建 root 节点处理错误的 update
function createRootErrorUpdate(
  fiber: Fiber,
  errorInfo: CapturedValue<mixed>,
  expirationTime: ExpirationTime,
): Update<mixed> {
  const update = createUpdate(expirationTime);
  // Unmount the root by rendering null.
  // 对比 setState 的 tag 为 UpdateState, 这里为捕获错误更新
  update.tag = CaptureUpdate;
  // Caution: React DevTools currently depends on this property
  // being called "element".
  // rootFiber 的 payload 为 element 属性的对象,这里置为 null,不渲染任何子节点
  update.payload = {element: null};
  const error = errorInfo.value;
  update.callback = () => {
    onUncaughtError(error);
    logError(fiber, errorInfo);
  };
  return update;
}
  • createClassErrorUpdate 创建 class 组件处理错误的 update
function createClassErrorUpdate(
  fiber: Fiber,
  errorInfo: CapturedValue<mixed>,
  expirationTime: ExpirationTime,
): Update<mixed> {
  // 创建 update
  const update = createUpdate(expirationTime);
  // 标记为捕获错误更新
  update.tag = CaptureUpdate;
  const getDerivedStateFromError = fiber.type.getDerivedStateFromError;
  // 如果有 getDerivedStateFromError 则将其返回结果设置为 payload 回调更新为 state
  if (typeof getDerivedStateFromError === 'function') {
    const error = errorInfo.value;
    update.payload = () => {
      return getDerivedStateFromError(error);
    };
  }

  const inst = fiber.stateNode;
  // 如果有 componentDidCatch 则加入到 callback 中,等到组件更新完后才调用
  if (inst !== null && typeof inst.componentDidCatch === 'function') {
    update.callback = function callback() {
      const error = errorInfo.value;
      const stack = errorInfo.stack;
      logError(fiber, errorInfo);
      this.componentDidCatch(error, {
        componentStack: stack !== null ? stack : '',
      });
    };
  }
  return update;
}

unwindWork

  • 类似 completeWork 对不同类型组件进行处理
  • 对于 effectshouldCapture 的组件设置 DidCapture 副作用
  • 主要处理 ClassComponent、HostRoot、SuspenseComponent 操作,其他类型组件只是 pop context
  • classComponent、SuspenseComponent 如果有 shouldCaptureeffectTag 则会返回 workInProgress,否则返回 null
  • HostRoot 总会返回 workInProgress,其他类型都返回 null
  • completeWork 的区别在于会判断 shouldCapture,如果有 shouldCapture,那么去掉 shouldCapture 并增加 DidCaptureeffectTag
function unwindWork(
  workInProgress: Fiber,
  renderExpirationTime: ExpirationTime,
) {
  switch (workInProgress.tag) {
    case ClassComponent: {
      const Component = workInProgress.type;
      if (isLegacyContextProvider(Component)) {
        popLegacyContext(workInProgress);
      }
      const effectTag = workInProgress.effectTag;
      // 如果有 shouldCapture 
      if (effectTag & ShouldCapture) {
        // 去掉 shouldCapture 增加 DidCapture
        workInProgress.effectTag = (effectTag & ~ShouldCapture) | DidCapture;
        return workInProgress;
      }
      return null;
    }
    case HostRoot: {
      popHostContainer(workInProgress);
      popTopLevelLegacyContextObject(workInProgress);
      const effectTag = workInProgress.effectTag;
      invariant(
        (effectTag & DidCapture) === NoEffect,
        'The root failed to unmount after an error. This is likely a bug in ' +
          'React. Please file an issue.',
      );
      workInProgress.effectTag = (effectTag & ~ShouldCapture) | DidCapture;
      return workInProgress;
    }
    case HostComponent: {
      popHostContext(workInProgress);
      return null;
    }
    case SuspenseComponent: {
      const effectTag = workInProgress.effectTag;
      if (effectTag & ShouldCapture) {
        workInProgress.effectTag = (effectTag & ~ShouldCapture) | DidCapture;
        // Captured a suspense effect. Set the boundary's `alreadyCaptured`
        // state to true so we know to render the fallback.
        const current = workInProgress.alternate;
        const currentState: SuspenseState | null =
          current !== null ? current.memoizedState : null;
        let nextState: SuspenseState | null = workInProgress.memoizedState;
        if (nextState === null) {
          // No existing state. Create a new object.
          nextState = {
            alreadyCaptured: true,
            didTimeout: false,
            timedOutAt: NoWork,
          };
        } else if (currentState === nextState) {
          // There is an existing state but it's the same as the current tree's.
          // Clone the object.
          nextState = {
            alreadyCaptured: true,
            didTimeout: nextState.didTimeout,
            timedOutAt: nextState.timedOutAt,
          };
        } else {
          // Already have a clone, so it's safe to mutate.
          nextState.alreadyCaptured = true;
        }
        workInProgress.memoizedState = nextState;
        // Re-render the boundary.
        return workInProgress;
      }
      return null;
    }
    case HostPortal:
      popHostContainer(workInProgress);
      return null;
    case ContextProvider:
      popProvider(workInProgress);
      return null;
    default:
      return null;
  }
}

portalComponent、ForwardRef、Mode、memo 更新

portalComponent

  • createPortal 创建 portal,返回一个类似 reactElement 的对象
function createPortal(
  children: ReactNodeList,
  container: DOMContainer,
  key: ?string = null,
) {
  invariant(
    isValidContainer(container),
    'Target container is not a DOM element.',
  );
  // TODO: pass ReactDOM portal implementation as third argument
  return ReactPortal.createPortal(children, container, null, key);
}

function createPortal(
  children: ReactNodeList,
  containerInfo: any,
  // TODO: figure out the API for cross-renderer implementation.
  implementation: any,
  key: ?string = null,
): ReactPortal {
  return {
    // This tag allow us to uniquely identify this as a React Portal
    $$typeof: REACT_PORTAL_TYPE,
    key: key == null ? null : '' + key,
    children,
    containerInfo,
    implementation,
  };
}
  • 调和子节点中为 portal 类型组件创建 fiber 节点
// reconcileChildFibers
function reconcileChildFibers(
  returnFiber: Fiber,
  currentFirstChild: Fiber | null,
  newChild: any,
  expirationTime: ExpirationTime,
): Fiber | null {
  // ....
  const isObject = typeof newChild === 'object' && newChild !== null;
  if (isObject) {
    switch (newChild.$$typeof) {
      case REACT_ELEMENT_TYPE:
        return placeSingleChild(
          reconcileSingleElement(
            returnFiber,
            currentFirstChild,
            newChild,
            expirationTime,
          ),
        );
      case REACT_PORTAL_TYPE:
        return placeSingleChild(
          reconcileSinglePortal(
            returnFiber,
            currentFirstChild,
            newChild,
            expirationTime,
          ),
        );
    }
  }
  // ....
}

// reconcileSinglePortal
function reconcileSinglePortal(
  returnFiber: Fiber,
  currentFirstChild: Fiber | null,
  portal: ReactPortal,
  expirationTime: ExpirationTime,
): Fiber {
  const key = portal.key;
  // 原有的子节点
  let child = currentFirstChild;
  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 === HostPortal &&
        // 原有子节点的 container 与新的 portal<createPortal返回的那个对象> 相同,说明可复用
        child.stateNode.containerInfo === portal.containerInfo &&
        child.stateNode.implementation === portal.implementation
      ) {
        // 删除兄弟节点
        deleteRemainingChildren(returnFiber, child.sibling);
        // 复用当前节点
        const existing = useFiber(
          child,
          portal.children || [],
          expirationTime,
        );
        existing.return = returnFiber;
        return existing;
      } else {
        // 不能复用删除所有
        deleteRemainingChildren(returnFiber, child);
        break;
      }
    } else {
      // key 不相同 删除所有
      deleteChild(returnFiber, child);
    }
    // 循环找知道找到可复用的节点为止
    child = child.sibling;
  }

  // 如果没有可以复用的,那么要创建新的 fiber

  const created = createFiberFromPortal(
    portal,
    returnFiber.mode,
    expirationTime,
  );
  created.return = returnFiber;
  return created;
}

createFiberFromPortal 创建 portal 类型的 fiber

function createFiberFromPortal(
  portal: ReactPortal,
  mode: TypeOfMode,
  expirationTime: ExpirationTime,
): Fiber {
  // 将 portal.children 直接赋值为 pendingProps
  const pendingProps = portal.children !== null ? portal.children : [];
  // 创建 fiber, tag 为 HostPortal
  const fiber = createFiber(HostPortal, pendingProps, portal.key, mode);
  fiber.expirationTime = expirationTime;

  // 因为 portal 有单独的挂载点,需要手动添加 stateNode
  // 参考 RootFiber.stateNode === FiberRoot
  fiber.stateNode = {
    containerInfo: portal.containerInfo,
    pendingChildren: null, // Used by persistent updates
    implementation: portal.implementation,
  };
  return fiber;
}

updatePortalComponent 更新 fiber 节点

case HostPortal:
  return updatePortalComponent(
    current,
    workInProgress,
    renderExpirationTime,
  );

function updatePortalComponent(
  current: Fiber | null,
  workInProgress: Fiber,
  renderExpirationTime: ExpirationTime,
) {
  // 有单独的挂载点,container 需要 push
  pushHostContainer(workInProgress, workInProgress.stateNode.containerInfo);
  // 获取新的 children
  const nextChildren = workInProgress.pendingProps;
  // 首次调用
  if (current === null) {
    // Portals are special because we don't append the children during mount
    // but at commit. Therefore we need to track insertions which the normal
    // flow doesn't do during mount. This doesn't happen at the root because
    // the root always starts with a "current" with a null child.
    // TODO: Consider unifying this with how the root works.
    workInProgress.child = reconcileChildFibers(
      workInProgress,
      null,
      nextChildren,
      renderExpirationTime,
    );
  } else {
    // 相当于 mountChildFibers
    // 更新渲染
    reconcileChildren(
      current,
      workInProgress,
      nextChildren,
      renderExpirationTime,
    );
  }
  return workInProgress.child;
}

ForwardRef

  • forwardRef 用来传递 ref
  • 调和子节点创建 fiber 是在 reconcileSingleElement -> createFiberFromElement -> createFiberFromTypeAndProps 中将 fiberTag 标记为 ForwardRef
function forwardRef<Props, ElementType: React$ElementType>(
  render: (props: Props, ref: React$Ref<ElementType>) => React$Node,
) {
  if (__DEV__) {
    if (typeof render !== 'function') {
      warningWithoutStack(
        false,
        'forwardRef requires a render function but was given %s.',
        render === null ? 'null' : typeof render,
      );
    } else {
      warningWithoutStack(
        // Do not warn for 0 arguments because it could be due to usage of the 'arguments' object
        render.length === 0 || render.length === 2,
        'forwardRef render functions accept exactly two parameters: props and ref. %s',
        render.length === 1
          ? 'Did you forget to use the ref parameter?'
          : 'Any additional parameter will be undefined.',
      );
    }

    if (render != null) {
      warningWithoutStack(
        render.defaultProps == null && render.propTypes == null,
        'forwardRef render functions do not support propTypes or defaultProps. ' +
          'Did you accidentally pass a React component?',
      );
    }
  }

  return {
    $$typeof: REACT_FORWARD_REF_TYPE,
    render, // render 即 function component
  };
}

updateForwardRef 更新

case ForwardRef: {
  const type = workInProgress.type;
  const unresolvedProps = workInProgress.pendingProps;
  const resolvedProps =
    workInProgress.elementType === type
      ? unresolvedProps
      : resolveDefaultProps(type, unresolvedProps);
  return updateForwardRef(
    current,
    workInProgress,
    type,
    resolvedProps,
    renderExpirationTime,
  );
}

// updateForwardRef
function updateForwardRef(
  current: Fiber | null,
  workInProgress: Fiber,
  type: any,
  nextProps: any,
  renderExpirationTime: ExpirationTime,
) {
  // render 即 React.forwardRef 传入的 function component
  const render = type.render;
  // 调用组件产生的 ref
  const ref = workInProgress.ref;
  if (hasLegacyContextChanged()) {
    // Normally we can bail out on props equality but if context has changed
    // we don't do the bailout and we have to reuse existing props instead.
  } else if (workInProgress.memoizedProps === nextProps) {
    // 之前的 ref 
    const currentRef = current !== null ? current.ref : null;
    // 两个ref 相同则跳过更新
    if (ref === currentRef) {
      return bailoutOnAlreadyFinishedWork(
        current,
        workInProgress,
        renderExpirationTime,
      );
    }
  }

  let nextChildren;
  if (__DEV__) {
    ReactCurrentOwner.current = workInProgress;
    ReactCurrentFiber.setCurrentPhase('render');
    nextChildren = render(nextProps, ref);
    ReactCurrentFiber.setCurrentPhase(null);
  } else {
    // React.forwardRef((props, ref) => {})
    // 传递 ref 并获取新 children,没有传 context
    nextChildren = render(nextProps, ref);
  }
  // 调和子节点
  reconcileChildren(
    current,
    workInProgress,
    nextChildren,
    renderExpirationTime,
  );
  return workInProgress.child;
}

Mode 组件

  • React 提供的原生组件,包括 <ConCurrentmode /><StrictMode />
  • 作用:在 Fiber 对象上记录所有子节点所对应的渲染模式,在创建更新时候根据 mode 创建 expirationTime

typeofMode

export type TypeOfMode = number;

export const NoContext = 0b000;
export const ConcurrentMode = 0b001;
export const StrictMode = 0b010;
export const ProfileMode = 0b100;
  • 更新调和子节点在 createFiberFromElement -> createFiberFromTypeAndProps 中为 mode 组件创建 fiber 节点,这里的传入的 modereturnFiber(即父组件) 上的 mode,而父组件上的 mode 最终来自 hostRootFiber 上,默认为 NoContext
  • 对应的 Mode组件 它会有一个特殊的的 Fiber 对象上的 mode 属性
    • conCurrentMode 传入的 modeNoContext | ConcurrentMode | StrictMode -> 0b011
    • StrictMode 传入的 ModeNoContext | StrictMode -> 0b010
  • 而在调和 Mode 组件 的子节点过程中,传入的 mode 则是这个 Mode 组件fiber 对象上的 mode 属性,对于 Mode 组件 它所有子树上的子节点都会具有这个 Mode 组件fiber 对象上的 mode 属性,以此来记录这个子树处于那个渲染模式下,才会有后期创建更新时根据 mode 属性如 ConcurrentMode 会对 expirationTime 有个特殊的计算过程
// 
function createHostRootFiber(isConcurrent: boolean): Fiber {
  // 这里在调用 ReactDOM.render 方法时 isConcurrent 为false,所以传递到子节点一直都是 NoContext
  let mode = isConcurrent ? ConcurrentMode | StrictMode : NoContext;

  if (enableProfilerTimer && isDevToolsPresent) {
    // Always collect profile timings when DevTools are present.
    // This enables DevTools to start capturing timing at any point–
    // Without some nodes in the tree having empty base times.
    mode |= ProfileMode;
  }

  return createFiber(HostRoot, null, null, mode);
}

// reconcileChildFibers
function reconcileChildFibers(
    returnFiber: Fiber,
    currentFirstChild: Fiber | null,
    newChild: any,
    expirationTime: ExpirationTime,
  ): Fiber | null {
  // ...
  case REACT_ELEMENT_TYPE:
    return placeSingleChild(
      reconcileSingleElement(
        returnFiber,
        currentFirstChild,
        newChild,
        expirationTime,
      ),
    );
  // ...  
}

// reconcileSingleElement

function reconcileSingleElement(
    returnFiber: Fiber,
    currentFirstChild: Fiber | null,
    element: ReactElement,
    expirationTime: ExpirationTime,
  ): Fiber {
    // ....

    // 创建新的节点
    if (element.type === REACT_FRAGMENT_TYPE) {
      // Fragment 传的elements传入的是 element.props.children, --> createFiber接收的 penddingProps 即这个 children
      const created = createFiberFromFragment(
        element.props.children,
        returnFiber.mode,
        expirationTime,
        element.key,
      );
      created.return = returnFiber;
      return created;
    } else {
      // 根据 element type来创建不同 fiber 对象
      const created = createFiberFromElement(
        element,
        returnFiber.mode,
        expirationTime,
      );
      created.ref = coerceRef(returnFiber, currentFirstChild, element);
      created.return = returnFiber;
      return created;
    }
}

// createFiberFromTypeAndProps
function createFiberFromTypeAndProps(
  type: any, // React$ElementType
  key: null | string,
  pendingProps: any,
  owner: null | Fiber,
  mode: TypeOfMode,
  expirationTime: ExpirationTime,
) {
  let fiber;

  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;
  if (typeof type === 'function') {
    if (shouldConstruct(type)) {
      // component 组件
      fiberTag = ClassComponent;
    }
  } else if (typeof type === 'string') {
    fiberTag = HostComponent;
  } else {
    getTag: switch (type) {
      case REACT_FRAGMENT_TYPE:
        return createFiberFromFragment(
          pendingProps.children,
          mode,
          expirationTime,
          key,
        );
      // concurrent mode
      // NoContext | ConcurrentMode | StrictMode -> 0b011
      case REACT_CONCURRENT_MODE_TYPE:
        return createFiberFromMode(
          pendingProps,
          mode | ConcurrentMode | StrictMode,
          expirationTime,
          key,
        );
      // strict mode
      // NoContext | StrictMode -> 0b010
      case REACT_STRICT_MODE_TYPE:
        return createFiberFromMode(
          pendingProps,
          mode | StrictMode,
          expirationTime,
          key,
        );
  // ....
  return fiber;
}

createFiberFromMode 根据 mode 创建 Fiber 节点

  • type 赋值:mode & ConcurrentMode 等于 NoContext 则为 StrictMode,否则为 ConCurrentMode
  • 依次给子节点也添加上父组件 mode 对应的 type
function createFiberFromMode(
  pendingProps: any,
  mode: TypeOfMode,
  expirationTime: ExpirationTime,
  key: null | string,
): Fiber {
  const fiber = createFiber(Mode, pendingProps, key, mode);

  // TODO: The Mode fiber shouldn't have a type. It has a tag.
  // 0b011 & 0b001 => 0b001 => ConCurrentMode
  // 0b010 & 0b001 => 0b000 => StrictMode
  const type =
    (mode & ConcurrentMode) === NoContext
      ? REACT_STRICT_MODE_TYPE
      : REACT_CONCURRENT_MODE_TYPE;
  fiber.elementType = type;
  fiber.type = type;

  fiber.expirationTime = expirationTime;
  return fiber;
}

updateMode更新 Mode

function updateMode(
  current: Fiber | null,
  workInProgress: Fiber,
  renderExpirationTime: ExpirationTime,
) {
  const nextChildren = workInProgress.pendingProps.children;
  // children 来源?
  reconcileChildren(
    current,
    workInProgress,
    nextChildren,
    renderExpirationTime,
  );
  return workInProgress.child;
}

Memo 组件

  • React.memo(com, compare) 创建一个具有 pureComponent 特性的 functionComponent

React.memo()

  • 调和子节点过程中,根据 $$typeof 标记 fiberTagMemoComponent
function memo<Props>(
  type: React$ElementType,
  compare?: (oldProps: Props, newProps: Props) => boolean,
) {
  return {
    $$typeof: REACT_MEMO_TYPE,
    // 这个 type 即传入进来的 function Component
    type,
    compare: compare === undefined ? null : compare,
  };
}

// function createFiberFromTypeAndProps 
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;
    }
  }

updateMemoComponent 更新 memo

  • memo 组件的 props 都是作用于传入的 function 组件内,memo 组件的意义在于进行一次组件包裹,可传入自定义比较函数
  • 创建 child 没有使用调和子节点方式,而是直接创建
  • compare 函数如果没传入,则使用 shallowEqual,返回 true 则跳过更新。
case MemoComponent: {
  const type = workInProgress.type;
  const unresolvedProps = workInProgress.pendingProps;
  const resolvedProps = resolveDefaultProps(type.type, unresolvedProps);
  return updateMemoComponent(
    current,
    workInProgress,
    type,
    resolvedProps,
    updateExpirationTime,
    renderExpirationTime,
  );
}

function updateMemoComponent(
  current: Fiber | null,
  workInProgress: Fiber,
  Component: any,
  nextProps: any,
  updateExpirationTime,
  renderExpirationTime: ExpirationTime,
): null | Fiber {
  // 首次渲染
  if (current === null) {
    // 拿到传入的那个 function component
    let type = Component.type;
    // 如果是纯函数组件没有 contruct、defaultProps 的组件且没有比较函数,则按 SimpleMemoComponent 更新
    if (isSimpleFunctionComponent(type) && Component.compare === null) {
      // If this is a plain function component without default props,
      // and with only the default shallow comparison, we upgrade it
      // to a SimpleMemoComponent to allow fast path updates.
      // 更新 tag,下次更新直接走 updateSimpleMemoComponent
      workInProgress.tag = SimpleMemoComponent;
      workInProgress.type = type;
      return updateSimpleMemoComponent(
        current,
        workInProgress,
        type,
        nextProps,
        updateExpirationTime,
        renderExpirationTime,
      );
    }
    // reconcileChildFibers 会把 props 用于当前组件本身的 children 调和的过程 
    // 而对于 memo 组件的 child 来自于传入的那个 function component ,因此要将 props 传递给 function component,来创建 children
    // 直接创建,而不是调和子节点,
    let child = createFiberFromTypeAndProps(
      Component.type,
      null,
      nextProps,
      null,
      workInProgress.mode,
      renderExpirationTime,
    );
    child.ref = workInProgress.ref;
    child.return = workInProgress;
    workInProgress.child = child;
    return child;
  }
  let currentChild = ((current.child: any): Fiber); // This is always exactly one child
  // 如果没有必要更新
  if (
    updateExpirationTime === NoWork ||
    updateExpirationTime > renderExpirationTime
  ) {
    // This will be the props with resolved defaultProps,
    // unlike current.memoizedProps which will be the unresolved ones.
    const prevProps = currentChild.memoizedProps;
    // Default to shallow comparison
    let compare = Component.compare;
    // compare 有则用,否则为 shallowEqual
    compare = compare !== null ? compare : shallowEqual;
    // 相同则跳过更新
    if (compare(prevProps, nextProps) && current.ref === workInProgress.ref) {
      return bailoutOnAlreadyFinishedWork(
        current,
        workInProgress,
        renderExpirationTime,
      );
    }
  }
  let newChild = createWorkInProgress(
    currentChild,
    nextProps,
    renderExpirationTime,
  );
  newChild.ref = workInProgress.ref;
  newChild.return = workInProgress;
  workInProgress.child = newChild;
  return newChild;
}

updateSimpleMemoComponent

function updateSimpleMemoComponent(
  current: Fiber | null,
  workInProgress: Fiber,
  Component: any, // memo 传入的 function component
  nextProps: any,
  updateExpirationTime,
  renderExpirationTime: ExpirationTime,
): null | Fiber {
  if (
    // 更新渲染
    current !== null && 
    // 优先级比当前低 或者 不需要更新
    (updateExpirationTime === NoWork ||
      updateExpirationTime > renderExpirationTime)
  ) {
    const prevProps = current.memoizedProps;
    // 如果 props 浅相同 并且 ref 相同 则跳过更新
    if (
      shallowEqual(prevProps, nextProps) &&
      current.ref === workInProgress.ref
    ) {
      return bailoutOnAlreadyFinishedWork(
        current,
        workInProgress,
        renderExpirationTime,
      );
    }
  }
  // 如果需要更新,纯 function component 按 function Component 方式更新
  return updateFunctionComponent(
    current,
    workInProgress,
    Component,
    nextProps,
    renderExpirationTime,
  );
}

FiberRoot和RootFiber

FiberRoot 与 RootFiber

FiberRoot

  • 整个应用的起点
  • 包含应用挂载的目标节点
  • 记录整个应用更新过程的各种信息

FiberRoot对象

  • FiberRootReactRoot 生成实例时调用 ReactFiberReconciler.jscreateContainer 传入 getElementById(root) 执行 ReactFiberRoot.js 中的createFiberRoot 函数生成的
export function createFiberRoot(
  containerInfo: any,
  isConcurrent: boolean,
  hydrate: boolean,
): FiberRoot {
  // 创建RootFiber
  const uninitializedFiber = createHostRootFiber(isConcurrent);

  let root;
  if (enableSchedulerTracing) {
    root = ({
      // root 节点对应的Fiber对象,fiber对象也是一个树结构,整个应用的顶点
      current: uninitializedFiber,
      // root 节点, render方法接收的第二个参数
      containerInfo: containerInfo,
      pendingChildren: null,

      earliestPendingTime: NoWork,
      latestPendingTime: NoWork,
      earliestSuspendedTime: NoWork,
      latestSuspendedTime: NoWork,
      latestPingedTime: NoWork,
      // 标记渲染过程中是否有错误
      didError: false,
      // 正在等待提交的任务的expirationTime
      pendingCommitExpirationTime: NoWork,
      // 记录一次更新渲染过程完成的任务,更新完输出到dom上是读取的这个属性
      finishedWork: null,
      // 用在 React.Suspense
      timeoutHandle: noTimeout,
      context: null,
      pendingContext: null,
      hydrate,
      // 标记此次更新要执行的是哪个优先级的任务,更新过程中会遍历到每个节点,每个节点如果有更新就会有自己的expirationTime, Root中会记录整个应用中优先级最高的expirationTime,在更新过程中会根据这个变量去进行更新,如果遍历到某个节点如果这个节点的expirationTime比它大,则说明这个节点的更新优先级排在后面。
      nextExpirationTimeToWorkOn: NoWork,
      // 用在调度过程中
      expirationTime: NoWork,
      firstBatch: null,
      // 如果存在多个root的情况,进行链表属性标记
      nextScheduledRoot: null,

      interactionThreadID: unstable_getThreadID(),
      memoizedInteractions: new Set(),
      pendingInteractionMap: new Map(),
    }: FiberRoot);
  } else {
    ...
  }

  // RootFiber 对象的stateNode 指向 FiberRoot
 // FiberRoot.current = RootFiber
  uninitializedFiber.stateNode = root;

  // The reason for the way the Flow types are structured in this file,
  // Is to avoid needing :any casts everywhere interaction tracing fields are used.
  // Unfortunately that requires an :any cast for non-interaction tracing capable builds.
  // $FlowFixMe Remove this :any cast and replace it with something better.
  return ((root: any): FiberRoot);
}

Fiber

  • 每一个 ReactElement 对应一个 Fiber 对象
  • 记录节点的各种状态
    • class Component 中的 this.statethis.props ,在 Fiber 更新后才会更新 class Component 上的 this.state, props,也是 hooks 实现的原理,functional Component 是没有 this.state this.props 的,Fiber 有能力记录这些状态之后在 functional Component 更新后拿到这些状态。
  • 串联整个应用形成树形结构

ReactElement 对应的树结构

reactElement

Fiber 数据结构

  function FiberNode(
    tag: WorkTag,
    pendingProps: mixed,
    key: null | string,
    mode: TypeOfMode,
  ) {
    // Instance
    // 标记不同的组件类型,不同的更新方式class component or functional
    this.tag = tag;
    // key
    this.key = key;
    // createElement 第一个参数,组件 或者 标签
    this.elementType = null;
    // 记录组件 resolved 后是 class 还是 functional component
    this.type = null;
    // 节点的实例,对应 class 组件的实例或者 dom 节点的实例, functional 组件没有实例就没有 stateNode
    this.stateNode = null;

    // Fiber
    // 指向他在Fiber节点树中的`parent`,用来在处理完这个节点之后向上返回
    this.return = null;
    // 单链表结构
    // 指向自己的第一个子节点
    this.child = null;
    // 指向自己的兄弟节点,兄弟节点的 return 指向同一个父节点
    this.sibling = null;
    this.index = 0;
    // ref
    this.ref = null;
    // 新的变动带来的新的 props
    this.pendingProps = pendingProps;
    // 老的 props
    this.memoizedProps = null;
    // 该Fiber节点对应的组件产生的 Update 会存放在这个队列里面
    this.updateQueue = null;
    // 上一次渲染完成后的老的 State, 新 state 是由 updateQueue 计算出来的然后覆盖这里
    this.memoizedState = null;
    // context 相关
    this.firstContextDependency = null;
    // 继承父节点的 mode
    this.mode = mode;

    // Effects 副作用 用来标记 dom 节点进行哪些更新,用来标记组件执行哪些生命周期
    this.effectTag = NoEffect;
    this.nextEffect = null;

    this.firstEffect = null;
    this.lastEffect = null;
    // 当前节点产生更新的任务的过期时间
    this.expirationTime = NoWork;
    // 子节点产生更新的过期时间
    this.childExpirationTime = NoWork;
    // 在 Fiber 树更新的过程中,每个 Fiber 都会创建一个跟其对应的 Fiber 称之为 workInProgress, 它与 current <==> workInProgress 一一对应,
    // current是当前的,workInProgress是要更新的,在更新完成后 workInProgress 是新的状态,current 是老的
    // 产生新的 update 要重新渲染,则渲染过程中会复用原有的 alternate,不用在每次更新时创建一个新的对象
    this.alternate = null;

    if (enableProfilerTimer) {
      this.actualDuration = 0;
      this.actualStartTime = -1;
      this.selfBaseDuration = 0;
      this.treeBaseDuration = 0;
    }

    if (__DEV__) {
      this._debugID = debugCounter++;
      this._debugSource = null;
      this._debugOwner = null;
      this._debugIsCurrentlyTiming = false;
      if (!hasBadMapPolyfill && typeof Object.preventExtensions === 'function') {
        Object.preventExtensions(this);
      }
    }
  }

ReactElement 对应的Fiber的结构
Fiber树结构

update 和 updateQueue

  • 用于记录组件状态的改变,记录改变的方式和内容

  • 存放于 updateQueue 中,存放多个 update 用来计算出最终改变的结果

  • 多个 update 可以同时存在, setState 三次会创建三个 update,放到 updateQueue 里,在进行更新的操作

update 数据结构

ReactDOM.render 最终调用 ReactRoot.prototype.render 时会执行到 scheduleRootUpdate 方法里执行 createUpdate

// ReactUpdateQueue.js

export function createUpdate(expirationTime: ExpirationTime): Update<*> {
  return {
    // 当前更新的过期时间
    expirationTime: expirationTime,
    // 四种状态
    // 更新updateState 0
    // 替换replaceState 1
    // 强制forceUpdate 2
    // throw 捕获 captureUpdate 3
    tag: UpdateState,
    // 实际执行的操作内容,更新的内容
    // 初次渲染传入的是元素 update.payload = { element } ,setState 可能传入的就是对象或者方法
    payload: null,
    callback: null,
    // 下一个 update 单向链表
    next: null,
    nextEffect: null,
  };
}

updateQueue 数据结构

export function createUpdateQueue<State>(baseState: State): UpdateQueue<State> {
  const queue: UpdateQueue<State> = {
    // 每次操作更新后计算出的state,作为下一次更新计算的基础
    baseState,
    // 记录链表结构
    firstUpdate: null,
    // 记录链表结构
    lastUpdate: null,
    // 记录链表结构
    firstCapturedUpdate: null,
    // 记录链表结构
    lastCapturedUpdate: null,
    firstEffect: null,
    lastEffect: null,
    firstCapturedEffect: null,
    lastCapturedEffect: null,
  };
  return queue;
}

enqueueUpdate 方法

enqueueUpdated 就是在 fiber 对象上创建一个 updateQueue,然后把 update 对象传入到这个 queue

export function enqueueUpdate<State>(fiber: Fiber, update: Update<State>) {
  // Update queues are created lazily.
  const alternate = fiber.alternate;
  let queue1;
  let queue2;
  // 通过 ReactDOM.render 初次渲染
  if (alternate === null) {
    // There's only one fiber.
    queue1 = fiber.updateQueue;
    queue2 = null;
    // 创建一个updateQueue
    if (queue1 === null) {
      queue1 = fiber.updateQueue = createUpdateQueue(fiber.memoizedState);
    }
  } else {
    // There are two owners.
    queue1 = fiber.updateQueue;
    queue2 = alternate.updateQueue;
    if (queue1 === null) {
      if (queue2 === null) {
        // Neither fiber has an update queue. Create new ones.
        queue1 = fiber.updateQueue = createUpdateQueue(fiber.memoizedState);
        queue2 = alternate.updateQueue = createUpdateQueue(
          alternate.memoizedState,
        );
      } else {
        // Only one fiber has an update queue. Clone to create a new one.
        queue1 = fiber.updateQueue = cloneUpdateQueue(queue2);
      }
    } else {
      if (queue2 === null) {
        // Only one fiber has an update queue. Clone to create a new one.
        queue2 = alternate.updateQueue = cloneUpdateQueue(queue1);
      } else {
        // Both owners have an update queue.
      }
    }
  }
  // 初次渲染 queue2 为null
  if (queue2 === null || queue1 === queue2) {
    // There's only a single queue.
    // 将 update 加入到 updateQueue 里
    // fiber.updateQueue.firstUpdate = fiber.updateQueue.lastUpdate = update;
    appendUpdateToQueue(queue1, update);
  } else {
    // There are two queues. We need to append the update to both queues,
    // while accounting for the persistent structure of the list — we don't
    // want the same update to be added multiple times.
    if (queue1.lastUpdate === null || queue2.lastUpdate === null) {
      // One of the queues is not empty. We must add the update to both queues.
      appendUpdateToQueue(queue1, update);
      appendUpdateToQueue(queue2, update);
    } else {
      // Both queues are non-empty. The last update is the same in both lists,
      // because of structural sharing. So, only append to one of the lists.
      appendUpdateToQueue(queue1, update);
      // But we still need to update the `lastUpdate` pointer of queue2.
      queue2.lastUpdate = update;
    }
  }
}

appendUpdateToQueue 方法,初次渲染将 queuefirstUpdatelastUpdate 都指向 update,之后的每有更新创建都将 lastUpdate.next 指向新的 update,将 lastUpdate 指向新的 update 构建链表结构

function appendUpdateToQueue<State>(
  queue: UpdateQueue<State>,
  update: Update<State>,
) {
  // Append the update to the end of the list.
  if (queue.lastUpdate === null) {
    // Queue is empty
    queue.firstUpdate = queue.lastUpdate = update;
  } else {
    // firstUpdate.next.next.next .... 为单链表结构
    // 上次的 next 和 这次的 lastUpdate 都指向新的 update,改变的是 lastUpdate 指针
    queue.lastUpdate.next = update;
    // 每次更新 lastUpdate
    queue.lastUpdate = update;
  }
}

expirationTime

updateContainer 方法中会计算一个 expirationTime 然后用这个时间创建 update 对象推入 updateQueue

export function updateContainer(
  element: ReactNodeList, // app
  container: OpaqueRoot, // FiberRoot
  parentComponent: ?React$Component<any, any>,
  callback: ?Function,
): ExpirationTime {
  // Fiber
  const current = container.current;
  // 创建一个时间差
  const currentTime = requestCurrentTime();
  // 计算出一个时间,ConcurrentMode 会用到, 计算出的是优先级时间
  const expirationTime = computeExpirationForFiber(currentTime, current);
  return updateContainerAtExpirationTime(
    element,
    container,
    parentComponent,
    expirationTime,
    callback,
  );
}

requestCurrentTime 方法返回一个固定的常量,调用 recomputeCurrentRendererTime 计算js加载完成到当前渲染时间的时间差值,这个差值范围小(没超过一个单位UNIT_SIZE)的值会在 msToExpirationTime 内被计算成同一个常数,最后赋值全局变量 currentRendererTime

function requestCurrentTime() {

  // 已经进入渲染的阶段
  if (isRendering) {
    return currentSchedulerTime;
  }

  // ReactDOM.render 执行
  if (
    nextFlushedExpirationTime === NoWork ||
    nextFlushedExpirationTime === Never
  ) {
    recomputeCurrentRendererTime();
    currentSchedulerTime = currentRendererTime;
    return currentSchedulerTime;
  }

  return currentSchedulerTime;
}

function recomputeCurrentRendererTime() {
  const currentTimeMs = now() - originalStartTimeMs;
  currentRendererTime = msToExpirationTime(currentTimeMs);
}

const UNIT_SIZE = 10;
const MAGIC_NUMBER_OFFSET = 2;

// 1 unit of expiration time represents 10ms.
export function msToExpirationTime(ms: number): ExpirationTime {
  // 除10 向下取整
  return ((ms / UNIT_SIZE) | 0) + MAGIC_NUMBER_OFFSET;
}

computeExpirationForFiber 方法会根据当前渲染的 currentTime 计算出一个优先级时间,核心就是根据渲染方式的 mode 不同来创建不同优先级的 expirationTime, 区别在于传入computeExpirationBucket 的参数不同。

  • Async 模式 会进行调度可能会被中断,会计算出 expirationTime 来分配优先级,
  • sync 模式优先级最高
  • computeExpirationForFiber 在异步 mode 的情况下才根据 currentTime 来计算 expirationTime
  • expirationTime 值选项有 Sync 1, NoWork 0,还有就是计算出来的时间值
  • 在 expirationContext 不为 NoWork 时,expirationContext 会根据更新 api 方式的不同设置为 Sync 或者 计算出 Async 方式的优先级时间
    • deferredUpdates 计算出 Async 方式的优先级时间
    • flushSync 为 Sync
function computeExpirationForFiber(currentTime: ExpirationTime, fiber: Fiber) {
  let expirationTime;
  // 外部强制的情况
    // 两种情况赋值 expirationContext ,
    // 1、deferredUpdates 计算出 Async 方式的优先级时间
    // 2、syncUpdates <- flushSync <- ReactDOM.flushSync , syncUpdates 在执行 fn 之前将 
   // expirationContext 修改为 Sync,通过外部强制某个更新必须使用那种 expirationTime 的行为
  if (expirationContext !== NoWork) {
    // An explicit expiration context was set;
    expirationTime = expirationContext;
  } else if (isWorking) {
  // 有任务更新的情况
    if (isCommitting) {
      // Updates that occur during the commit phase should have sync priority
      // by default.
      expirationTime = Sync;
    } else {
      // Updates during the render phase should expire at the same time as
      // the work that is being rendered.
      expirationTime = nextRenderExpirationTime;
    }
  } else {
    // No explicit expiration context was set, and we're not currently
    // performing work. Calculate a new expiration time.
    // ConcurrentMode = 0b001; 通过 与 / 或运算便于组合和判断不同的mode
    // 异步 mode 才计算 expirationTime
    if (fiber.mode & ConcurrentMode) {
      // interactiveUpdates 函数里置为true,即计算高优先级的expirationTime
      if (isBatchingInteractiveUpdates) {
        // This is an interactive update
        expirationTime = computeInteractiveExpiration(currentTime);
      } else {
        // This is an async update
        expirationTime = computeAsyncExpiration(currentTime);
      }
      // If we're in the middle of rendering a tree, do not update at the same
      // expiration time that is already rendering.
      if (nextRoot !== null && expirationTime === nextRenderExpirationTime) {
        expirationTime += 1;
      }
    // 同步的更新
    } else {
      // This is a sync update
      expirationTime = Sync;
    }
  }
  if (isBatchingInteractiveUpdates) {
    // ...
  }
  return expirationTime;
}

computeExpirationBucket 最终的公式为 ((((currentTime - 2 + 5000(或者150) / 10) / 25(或者10)) | 0) + 1) * 25(或者10),| 0 向下取整,使得在没超过一个单位 25(10)范围内的时间计算出来的值都相同,这样在很短时间内多次 setState 调用更新时,也可以保证是同一优先级的更新。

// 高优先级(动画、交互)
export const HIGH_PRIORITY_EXPIRATION = __DEV__ ? 500 : 150;
export const HIGH_PRIORITY_BATCH_SIZE = 100;

export function computeInteractiveExpiration(currentTime: ExpirationTime) {
  return computeExpirationBucket(
    currentTime,
    HIGH_PRIORITY_EXPIRATION,
    HIGH_PRIORITY_BATCH_SIZE,
  );
}

// 低优先级,每 25 往上加的,前后差距在25ms内计算出来的值都一样的。
export const LOW_PRIORITY_EXPIRATION = 5000;
export const LOW_PRIORITY_BATCH_SIZE = 250;

export function computeAsyncExpiration(
  currentTime: ExpirationTime,
): ExpirationTime {
  return computeExpirationBucket(
    currentTime,
    LOW_PRIORITY_EXPIRATION,
    LOW_PRIORITY_BATCH_SIZE,
  );
}

function ceiling(num: number, precision: number): number {
  return (((num / precision) | 0) + 1) * precision;
}

function computeExpirationBucket(
  currentTime,
  expirationInMs,
  bucketSizeMs,
): ExpirationTime {
  return (
    MAGIC_NUMBER_OFFSET +
    ceiling(
      currentTime - MAGIC_NUMBER_OFFSET + expirationInMs / UNIT_SIZE,
      bucketSizeMs / UNIT_SIZE,
    )
  );
}

上一篇
下一篇

FunctionComponent 更新过程

函数式组件(FunctionComponent)更新过程

case FunctionComponent: {
  const Component = workInProgress.type;
  const unresolvedProps = workInProgress.pendingProps;
  const resolvedProps =
    workInProgress.elementType === Component
      ? unresolvedProps
      : resolveDefaultProps(Component, unresolvedProps);
  return updateFunctionComponent(
    // workInProgress.alternate 当前fiber
    current,
    workInProgress,
    // 组件类型
    Component,
    resolvedProps,
    // 最高更新优先级
    renderExpirationTime,
  );
}

updateFunctionComponent

function updateFunctionComponent(
  current,
  workInProgress,
  Component,
  nextProps: any,
  renderExpirationTime,
) {
  const unmaskedContext = getUnmaskedContext(workInProgress, Component, true);
  const context = getMaskedContext(workInProgress, unmaskedContext);

  let nextChildren;
  prepareToReadContext(workInProgress, renderExpirationTime);
  if (__DEV__) {
    // ...
  } else {
    // Component 即组件的那个方法
    nextChildren = Component(nextProps, context);
  }

  // React DevTools reads this flag.
  workInProgress.effectTag |= PerformedWork;
  // 把 nextChildren 这些 ReactElement 变成 Fiber 对象, 挂载 在 workInProgress.child 上
  reconcileChildren(
    current,
    workInProgress,
    nextChildren,
    renderExpirationTime,
  );
  return workInProgress.child;
}

reconcileChildren

  • 根据 fiber.children 生成 fiber 子树
  • 判断 Fiber 对象是否可以复用,在第一次渲染就渲染了 fiber 子树,state 变化可能会导致某些子节点产生变化而不能复用,但是大部分是可以复用的
  • 列表根据 key 优化
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,
    );
  }
}

// 更新渲染
export const reconcileChildFibers = ChildReconciler(true);
// 首次渲染
export const mountChildFibers = ChildReconciler(false);

reconcileChildFibers

  • reconcileChildFibersChildReconciler 最终返回的函数
  • 先判断 newChild 是不是 Fragment 节点,如果是 Fragment 则将 newChild 赋值为 newChild.props.children
  • 接着判断 newChild 是不是 isObject
    • REACT_ELEMENT_TYPEreconcileSingleElement
    • REACT_PORTAL_TYPEreconcileSinglePortal
  • 判断 string or numberreconcileSingleTextNode
  • 判断 isArrayreconcileChildrenArray
  • 判断是 iterator 函数: reconcileChildrenIterator
  • 都不符合以上情况,并且还是 isObject,则抛出错误
  • newChildundefined 并且非 Fragment,提醒没有返回值
  • null 的情况,新的 propschildrennull,则把现有的所有 children 都删掉
function reconcileChildFibers(
    // workInProgress
    returnFiber: Fiber,
    // 原有子节点 current.child
    currentFirstChild: Fiber | null,
    // 组件执行后返回的 nextChildren, nextChildren = Component(nextProps, context);
    newChild: any,
    // 最高优先级过期时间
    expirationTime: ExpirationTime,
  ): Fiber | null {
    // 判断是不是 Fragment
    const isUnkeyedTopLevelFragment =
      typeof newChild === 'object' &&
      newChild !== null &&
      newChild.type === REACT_FRAGMENT_TYPE &&
      newChild.key === null;
      // 如果是 Fragment,只渲染 props.children 就行了
    if (isUnkeyedTopLevelFragment) {
      newChild = newChild.props.children;
    }

    // Handle object types
    const isObject = typeof newChild === 'object' && newChild !== null;
    // 对象类型
    if (isObject) {
      switch (newChild.$$typeof) {
        // React.element
        case REACT_ELEMENT_TYPE:
          return placeSingleChild(
            reconcileSingleElement(
              returnFiber,
              currentFirstChild,
              newChild,
              expirationTime,
            ),
          );
        // React.portal
        case REACT_PORTAL_TYPE:
          return placeSingleChild(
            reconcileSinglePortal(
              returnFiber,
              currentFirstChild,
              newChild,
              expirationTime,
            ),
          );
      }
    }
    // 文本类型
    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,
      );
    }
    // 还是对象那么就抛出错误
    if (isObject) {
      throwOnInvalidObjectType(returnFiber, newChild);
    }
    if (typeof newChild === 'undefined' && !isUnkeyedTopLevelFragment) {
      // 没有返回值的情况,给提示
    }
    // 为 null 的情况,删除所有子节点
    // Remaining cases are all treated as empty.
    return deleteRemainingChildren(returnFiber, currentFirstChild);
  }

React Element 类型——reconcileSingleElement

  • 找可以复用的节点:从当前已有的所有子节点当中找到一个可以复用新的子节点的那个fiber对象,复用它然后直接返回,把剩下的兄弟节点删掉
  • 通过判断 elementType 来创建新的节点
function reconcileSingleElement(
  returnFiber: Fiber,
  currentFirstChild: Fiber | null,
  element: ReactElement,
  expirationTime: ExpirationTime,
): Fiber {
  // 新 child 的key
  const key = element.key;
  // 已有的fiber节点,更新渲染才不为 null
  let child = currentFirstChild;

  while (child !== null) {
    // TODO: If key === null and child.key === null, then this only applies to
    // the first item in the list.
    // 判断新老的key是否相同
    if (child.key === key) {
      if (
        // 原 child 是 Fragment 且新 element type 也是 Fragment
        child.tag === Fragment
          ? element.type === REACT_FRAGMENT_TYPE
          // 或者判断 elementType 是否相同
          : child.elementType === element.type
      ) {
        // 复用当前节点,删除不用的兄弟节点(这里删除都是标记删除,而非真正删除)
        deleteRemainingChildren(returnFiber, child.sibling);
        // 复用这个老的child
        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 existing;
      } else {
        // key 相同,但类型不相同,删除老的child,退出循环
        deleteRemainingChildren(returnFiber, child);
        break;
      }
    } else {
      // 不相同则全部删除
      deleteChild(returnFiber, child);
    }
    // 兄弟节点继续寻找可复用的
    child = child.sibling;
  }
  // 创建新的节点
  if (element.type === REACT_FRAGMENT_TYPE) {
    // Fragment 传的elements传入的是 element.props.children, --> createFiber接收的 penddingProps 即这个 children
    const created = createFiberFromFragment(
      element.props.children,
      returnFiber.mode,
      expirationTime,
      element.key,
    );
    created.return = returnFiber;
    return created;
  } else {
    // 根据 element type来创建不同 fiber 对象
    const created = createFiberFromElement(
      element,
      returnFiber.mode,
      expirationTime,
    );
    created.ref = coerceRef(returnFiber, currentFirstChild, element);
    created.return = returnFiber;
    return created;
  }
}

useFiber 复用节点

function useFiber(
    fiber: Fiber,
    pendingProps: mixed,
    expirationTime: ExpirationTime,
  ): Fiber {
    // 复用的时候 fiber.alternate 已不为 null,就不需要再 creatFiber 了
    const clone = createWorkInProgress(fiber, pendingProps, expirationTime);
    clone.index = 0;
    clone.sibling = null;
    return clone;
  }

deleteChild 标记删除 和 deleteRemainingChildren

function deleteChild(returnFiber: Fiber, childToDelete: Fiber): void {
  if (!shouldTrackSideEffects) {
    // Noop.
    return;
  }
  const last = returnFiber.lastEffect;
  if (last !== null) {
    last.nextEffect = childToDelete;
    returnFiber.lastEffect = childToDelete;
  } else {
    returnFiber.firstEffect = returnFiber.lastEffect = childToDelete;
  }
  // 清空副作用
  childToDelete.nextEffect = null;
  // 标记删除,更新 fiberTree,直接删除会影响到 dom 节点,真正删除在 commit 阶段,这里的任务是可以中断的,而commit 是不可中断的
  childToDelete.effectTag = Deletion;
}

function deleteRemainingChildren(
  returnFiber: Fiber, // 当前更新的节点
  currentFirstChild: Fiber | null, // 子节点
): null {
  // 首次渲染 直接返回
  if (!shouldTrackSideEffects) {
    // Noop.
    return null;
  }

  let childToDelete = currentFirstChild;
  // 循环删除要删除的子节点的兄弟节点
  while (childToDelete !== null) {
    deleteChild(returnFiber, childToDelete);
    childToDelete = childToDelete.sibling;
  }
  return null;
}

数组类型——reconcileChildrenArray

  • 尽量减少数组的遍历次数来达到判断节点是否可复用的过程

  • 第一次遍历

    • 主要目的是找到不能复用的那个节点为止则跳出循环,newIdx 则为能够有几个能够复用的节点的index
    • updateSlot 中通过判断新老 Key 是否相同来复用
      • updateSlot 返回 null 表示不能复用, 直接 break
      • 文本节点:oldFiberkey,而 newChildtextNode 直接返回 null , 因为文本节点是没有 Key
      • 对于 textNode、Fragment、Element 都会判断 oldFiber 不为 null 就复用,为 null 则创建新的
    • 复用则构建链结构
  • 第一次遍历完 或者 break

    • newIdx 等于 newChildren.length 说明已经在 updateSlot 中创建新的对象了, 新数组操作完成了, 所有新节点都已经创建
      • 直接标记删除 oldFiber
    • oldFibernull,老的节点被复用完了,遍历到最后没有能复用的节点了,新的还有部分没创建, 则直接创建新的
  • 最后 newChildren 还没创建完,oldFiber 还不为 null 的情况,数组存在顺序的变化

    • 这是一次完整的遍历
    • updateFromMap: 根据 keyindex 创建 map 对象,通过 map 对象在 oldFiber 里找到可以复用的或者创建新的
function reconcileChildrenArray(
  returnFiber: Fiber,
  currentFirstChild: Fiber | null,
  newChildren: Array<*>,
  expirationTime: ExpirationTime,
): Fiber | null {
  // 要返回的结果
  let resultingFirstChild: Fiber | null = null;
  // 用于构建链结构的中间变量
  let previousNewFiber: Fiber | null = null;

  // 上一次渲染完成后第一个 child 节点, current.child
  let oldFiber = currentFirstChild;
  let lastPlacedIndex = 0;
  let newIdx = 0;
  // 下一个老的节点
  let nextOldFiber = null;
  for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) {
    // 老的 fiber 的 index 位置与新的不同,位置不匹配
    if (oldFiber.index > newIdx) {
      // 将 oldFiber 赋值给 nextOldFiber 暂存
      nextOldFiber = oldFiber;
      oldFiber = null;
    } else {
      // 没变化则下一个老的节点为当前的节点的兄弟节点
      nextOldFiber = oldFiber.sibling;
    }
    // 
    const newFiber = updateSlot(
      returnFiber,
      oldFiber,
      newChildren[newIdx],
      expirationTime,
    );
    // key 相同根据类型确定复用类型
    // 文本节点没有key,为 null 表示不能复用, 能复用也有复用复用节点和新建节点的区分 
    if (newFiber === null) {
      // TODO: This breaks on empty slots like null children. That's
      // 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,跳出循环,即找到不能复用的那个节点为止的fiber,那么 newIdx 则表示有多少个节点相同的那个index
      if (oldFiber === null) {
        oldFiber = nextOldFiber;
      }
      break;
    }
    // 更新渲染的情况
    if (shouldTrackSideEffects) {
      // 没有复用,说明这是这是创建新的节点, 则要标记删除老的
      if (oldFiber && newFiber.alternate === null) {
        // We matched the slot, but we didn't 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 we're 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 don't want to call updateSlot
      // with the previous one.
      // 构建链表结构
      previousNewFiber.sibling = newFiber;
    }
    previousNewFiber = newFiber;
    oldFiber = nextOldFiber;
  }
  // 新数组操作完,所有新的 children 都全部创建 fiber 节点了
  if (newIdx === newChildren.length) {
    // We've reached the end of the new children. We can delete the rest.
    // 删除老的
    deleteRemainingChildren(returnFiber, oldFiber);
    // 返回首个子节点,其他节点是通过 sibling 指向下去
    return resultingFirstChild;
  }

  if (oldFiber === null) {
    // If we don't 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;
      }
      previousNewFiber = newFiber;
    }
    return resultingFirstChild;
  }

  // Add all children to a key map for quick lookups.
  // newChildren 还没创建完,oldFiber 还不为null
  // 数组存在顺序的变化,在 oldFiber 里找到可以复用的,通过 map 快速寻找
  // 根据 key 或 index 创建 map 对象
  const existingChildren = mapRemainingChildren(returnFiber, oldFiber);

  // 一次完整的遍历
  // Keep scanning and use the map to restore deleted items as moves.
  for (; newIdx < newChildren.length; newIdx++) {
    // 根据 map 找可以复用的节点 或 创建新节点,跟 updateSlot 相似
    const newFiber = updateFromMap(
      existingChildren,
      returnFiber,
      newIdx,
      newChildren[newIdx],
      expirationTime,
    );
    if (newFiber) {
      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 don't add it to the deletion
          // list.
          // 从 map 里删除
          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 weren't consumed above were deleted. We need
    // to add them to the deletion list.
    // 最后剩下的是没有被复用的,全部删除
    existingChildren.forEach(child => deleteChild(returnFiber, child));
  }

  return resultingFirstChild;
}

IndeterminateComponent、HostComponent、HostText 类型更新

IndeterminateComponent

  • 未指定类型的 Component
  • 创建时期
    • 调用 ReactDOM.render App 组件时会创建 FiberRoot -> createHostRootFiber,标记这个 fibertagHostRoot
      function createFiberRoot(
        containerInfo: any,
        isConcurrent: boolean,
        hydrate: boolean,
      ): FiberRoot {
        // Cyclic construction. This cheats the type system right now because
        // stateNode is any.
        const uninitializedFiber = createHostRootFiber(isConcurrent);
        // ....
      }
    
      function createHostRootFiber(isConcurrent: boolean): Fiber {
        let mode = isConcurrent ? ConcurrentMode | StrictMode : NoContext;
    
        if (enableProfilerTimer && isDevToolsPresent) {
          // Always collect profile timings when DevTools are present.
          // This enables DevTools to start capturing timing at any point–
          // Without some nodes in the tree having empty base times.
          mode |= ProfileMode;
        }
    
        return createFiber(HostRoot, null, null, mode);
      }
    • 第一次执行到 beginWork 时首次更新的是 HostRoot,在 updateHostRoot 中会调用 reconcileChildFibers 调和子节点,reconcileSingleElement -> createFiberFromElement -> createFiberFromTypeAndProps, 默认 fiberTagIndeterminateComponent,只有当 class 组件有 construct时才标记为class` 组件,

    • function 类型组件开始默认都是 IndeterminateComponent

function createFiberFromTypeAndProps(
  type: any, // React$ElementType
  key: null | string,
  pendingProps: any,
  owner: null | Fiber,
  mode: TypeOfMode,
  expirationTime: ExpirationTime,
): Fiber {
  let fiber;

  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;
  if (typeof type === 'function') {
    if (shouldConstruct(type)) {
      // component 组件
      fiberTag = ClassComponent;
    }
  // 原生 dom 标签
  } 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;
          }
        }
       // ...
      }
    }
  }

  fiber = createFiber(fiberTag, pendingProps, key, mode);
  fiber.elementType = type;
  fiber.type = resolvedType;
  fiber.expirationTime = expirationTime;

  return fiber;
}

在 mountIndeterminateComponent 中确定 function 组件类型

case IndeterminateComponent: {
  const elementType = workInProgress.elementType;
  return mountIndeterminateComponent(
    current,
    workInProgress,
    elementType,
    renderExpirationTime,
  );
}

function mountIndeterminateComponent(
  _current,
  workInProgress,
  Component,
  renderExpirationTime,
) {
  // 只有在第一次渲染的时候,才会有 mountIndeterminateComponent 这种情况,非首次渲染,已经发现 mountIndeterminateComponent 的具体的类型
  // 如果在第一次渲染中,current 存在,可能是中途抛出 error 或者 promise的情况,就是 suspended 组件的情况,把 current 和 workInprogress 的联系去除,需要重新创建 workInprogress,重新进行一次初次渲染的流程
  if (_current !== null) {
    // An indeterminate component only mounts if it suspended inside a non-
    // concurrent tree, in an inconsistent state. We want to tree it like
    // a new mount, even though an empty version of it already committed.
    // Disconnect the alternate pointers.
    _current.alternate = null;
    workInProgress.alternate = null;
    // Since this is conceptually a new fiber, schedule a Placement effect
    workInProgress.effectTag |= Placement;
  }
  // context 相关
  const props = workInProgress.pendingProps;
  const unmaskedContext = getUnmaskedContext(workInProgress, Component, false);
  const context = getMaskedContext(workInProgress, unmaskedContext);

  prepareToReadContext(workInProgress, renderExpirationTime);

  let value;

  if (__DEV__) {
    // ...
  } else {
    // 调用方法
    value = Component(props, context);
  }
  // React DevTools reads this flag.
  workInProgress.effectTag |= PerformedWork;

  if (
    typeof value === 'object' &&
    value !== null &&
    // 符合 class 组件条件
    // 这里如果函数里返回一个具有 render Function 属性的对象,也会被当作 class 组件调用
    typeof value.render === 'function' &&
    value.$$typeof === undefined
  ) {
    // Proceed under the assumption that this is a class instance
    workInProgress.tag = ClassComponent;

    // Push context providers early to prevent context stack mismatches.
    // During mounting we don't know the child context yet as the instance doesn't exist.
    // We will invalidate the child context in finishClassComponent() right after rendering.
    let hasContext = false;
    if (isLegacyContextProvider(Component)) {
      hasContext = true;
      pushLegacyContextProvider(workInProgress);
    } else {
      hasContext = false;
    }

    workInProgress.memoizedState =
      value.state !== null && value.state !== undefined ? value.state : null;

    const getDerivedStateFromProps = Component.getDerivedStateFromProps;
    if (typeof getDerivedStateFromProps === 'function') {
      applyDerivedStateFromProps(
        workInProgress,
        Component,
        getDerivedStateFromProps,
        props,
      );
    }

    adoptClassInstance(workInProgress, value);
    mountClassInstance(workInProgress, Component, props, renderExpirationTime);
    return finishClassComponent(
      null,
      workInProgress,
      Component,
      true,
      hasContext,
      renderExpirationTime,
    );
  } else {
    // 否则标记为 FunctionComponent,直接调和子节点
    // Proceed under the assumption that this is a function component
    workInProgress.tag = FunctionComponent;
    if (__DEV__) {
      // ...
    }
    // value 即子节点
    reconcileChildren(null, workInProgress, value, renderExpirationTime);
    return workInProgress.child;
  }
}

HostComponent

  • dom 原生标签对应的 fiber 节点
  • 没有 state 概念
  • 根据标签 typeprops 判断标签内容是不是纯文本,如果是纯文本则没有新子节点;如果不是并且 prevProps 是纯文本那么标记 effectTag
  • 最后调和创建子节点
function updateHostComponent(current, workInProgress, renderExpirationTime) {
  pushHostContext(workInProgress);

  if (current === null) {
    // 服务端渲染,复用节点
    tryToClaimNextHydratableInstance(workInProgress);
  }

  const type = workInProgress.type;
  const nextProps = workInProgress.pendingProps;
  const prevProps = current !== null ? current.memoizedProps : null;

  let nextChildren = nextProps.children;
  // 判断是否为纯文本节点的 children
  const isDirectTextChild = shouldSetTextContent(type, nextProps);

  if (isDirectTextChild) {
    // 纯文本节点,则没有children
    nextChildren = null;
  } else if (prevProps !== null && shouldSetTextContent(type, prevProps)) {
    // If we're switching from a direct text child to a normal child, or to
    // empty, we need to schedule the text content to be reset.
    // 原来的 props 值是纯文本,新的 props 是节点,标记替换
    workInProgress.effectTag |= ContentReset;
  }
  
  // 标记 ref,仅有在 classComponent 和 HostComponent (原生标签)中标记ref
  markRef(current, workInProgress);

  // Check the host config to see if the children are offscreen/hidden.
  if (
    renderExpirationTime !== Never &&
    workInProgress.mode & ConcurrentMode && // ConcurrentMode
    shouldDeprioritizeSubtree(type, nextProps) // !!props.hidden;
  ) {
    // 异步渲染模式下,具有 hidden 属性的组件永远不被更新
    // Schedule this fiber to re-render at offscreen priority. Then bailout.
    workInProgress.expirationTime = Never;
    return null;
  }

  // 调和创建子节点
  reconcileChildren(
    current,
    workInProgress,
    nextChildren,
    renderExpirationTime,
  );
  return workInProgress.child;
}

// shouldSetTextContent
// 只能渲染文本的标签 textarea、option、noscript
// children 为 string、number直接渲染为文本
// 使用了 dangerouslySetInnerHTML 属性的
function shouldSetTextContent(type: string, props: Props): boolean {
  return (
    type === 'textarea' ||
    type === 'option' ||
    type === 'noscript' ||
    typeof props.children === 'string' ||
    typeof props.children === 'number' ||
    (typeof props.dangerouslySetInnerHTML === 'object' &&
      props.dangerouslySetInnerHTML !== null &&
      props.dangerouslySetInnerHTML.__html != null)
  );
}

HostText

  • 纯文本节点,没有子节点
  • complete 阶段才插入到 dom
function updateHostText(current, workInProgress) {
  if (current === null) {
    // 服务端渲染
    tryToClaimNextHydratableInstance(workInProgress);
  }
  // Nothing to do here. This is terminal. We'll do the completion step
  // immediately after.
  return null;
}

Ref

ref

  • 创建 fiber 的时候(调和子节点里面)处理 ref
  • commit 开始前,第二次循环执行 commitAllHostEffectsdom的增、删、改)过程中,调用 commitDetachRefref 从之前挂载的地方卸载
  • 第三次循环执行 commitAllLifeCycles 调用 commitAttachRef更新过后的节点挂载到 ref 上,如果 ref 是函数 则执行 ref 函数fiberinstance 作为参数传入
// createRef
function createRef(): RefObject {
  const refObject = {
    current: null,
  };
  if (__DEV__) {
    Object.seal(refObject);
  }
  return refObject;
}

coerceRef

  • 处理字符串 ref,需要创建一个 ref 方法,挂载在 fiberref 属性上,在这个方法里将更新过的 dom 节点或者 class instance 挂载在实例的 refs[stringRef] 上(即 commitAttachRef 过程)
  • 最终使用是通过 this.refs.stringRef 获取 ref,即将被废弃
// ReactChildFiber.js 调和子节点复用或者创建新的fiber 节点时处理 ref
function coerceRef(
  returnFiber: Fiber,
  current: Fiber | null,
  element: ReactElement,
) {
  let mixedRef = element.ref;
  if (
    mixedRef !== null &&
    typeof mixedRef !== 'function' &&
    typeof mixedRef !== 'object'
  ) {
    // 处理字符串 ref 
    // createElement -> 传入 owner 为 ReactCurrentOwner.current ,而在 finishClassComponent 中设置 ReactCurrentOwner.current= workInProgress;
    // 之后子节点会在 instance.render 当中被创建,进入到调和子节点中
    // _owner 即 fiber 对象
    if (element._owner) {
      const owner: ?Fiber = (element._owner: any);
      let inst;
      if (owner) {
        const ownerFiber = ((owner: any): Fiber);
        // classComponent 的实例
        inst = ownerFiber.stateNode;
      }
      const stringRef = '' + mixedRef;
      // Check if previous string ref matches new string ref
      if (
        current !== null &&
        current.ref !== null &&
        typeof current.ref === 'function' &&
        current.ref._stringRef === stringRef // _stringRef 如果没有变化,不需要重新生成 ref  方法
      ) {
        return current.ref;
      }
      const ref = function(value) {
        //  即组件里使用的 this.refs 
        let refs = inst.refs;
        if (refs === emptyRefsObject) {
          // This is a lazy pooled frozen object, so we need to initialize.
          refs = inst.refs = {};
        }
        // dom 节点 或者 instance 被挂载的时候会调用 ref 这个方法,value 即传入它自己的实例
        if (value === null) {
          delete refs[stringRef];
        } else {
          // 设置到 this.refs 的 stringRef 属性上
          refs[stringRef] = value;
        }
      };
      ref._stringRef = stringRef;
      // 这个方法在 commit 阶段被调用
      return ref;
    } else {
      // 。。。
    }
  }
  return mixedRef;
}

commitDetachRef

  • commitAllHostEffects 先卸载原有的 ref
// dom 节点的插入、删除、更新
function commitAllHostEffects() {
  while (nextEffect !== null) {
    recordEffect();

    const effectTag = nextEffect.effectTag;

    // 判断内部是否为文字节点
    if (effectTag & ContentReset) {
      // 文字节点设置为空字符串
      commitResetTextContent(nextEffect);
    }

    // ref操作
    if (effectTag & Ref) {
      const current = nextEffect.alternate;
      // 非首次渲染
      if (current !== null) {
        commitDetachRef(current);
      }
    }
    // ...
  }
}

// commitDetachRef

function commitDetachRef(current: Fiber) {
  const currentRef = current.ref;
  if (currentRef !== null) {
    if (typeof currentRef === 'function') {
      // 如果是函数则传入 null 执行
      currentRef(null);
    } else {
      // 其他类型直接赋值 null
      currentRef.current = null;
    }
  }
}

commitAttachRef

  • commitAllLifeCycles 中将更新后的 instance 挂载到 ref
function commitAttachRef(finishedWork: Fiber) {
  const ref = finishedWork.ref;
  if (ref !== null) {
    const instance = finishedWork.stateNode;
    let instanceToUse;
    // 获取 instance 
    switch (finishedWork.tag) {
      case HostComponent:
        instanceToUse = getPublicInstance(instance);
        break;
      default:
        instanceToUse = instance;
    }
    if (typeof ref === 'function') {
      // 这里即函数式 ref 真正挂载 instance, 字符串 ref 创建的 ref 函数也在这执行,将 this.refs[stringRef] = instanceToUse
      ref(instanceToUse);
    } else {
      // 其他类型直接赋值 ref.current
      ref.current = instanceToUse;
    }
  }
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.