1.Fiber

在 react15 的 diff 算法里是一气呵成的更新 state,一旦有动画还有太大的 state 就会造成卡顿,不停的循环 Dom 树

{f:'div',type:'dom',props:{},children:[{[...]},{[...]},{[...]}]}

在 react16 中的 diff 算法 fiber,fiber 最大的改动就是讲数组结构改成了列表结构,在 fiber 中使用了 window.requestIdleCallback 来优化 diff 算法

{f:'div',type:'dom',props:{},next:'x86dasa',pre:'vdasd98'}
//其实就是使用索引指向了一个内存地址

在 react16 之前采用的算法,父组件里调子组件,可以类比为函数的递归,在 setState 后,react 会立即开始 reconciliation 过程,从父节点(Virtual DOM)开始遍历,以找出不同。将所有的 Virtual DOM 遍历完成后,reconciler 才能给出当前需要修改真实 DOM 的信息,并传递给 renderer,进行渲染,然后屏幕上才会显示此次更新内容。对于特别庞大的 vDOM 树来说,reconciliation 过程会很长(x00ms),在这期间,主线程是被 js 占用的,因此任何交互、布局、渲染都会停止,给用户的感觉就是页面被卡住了。

什么是 Fiber

大家应该都清楚进程(Process)和线程(Thread)的概念,在计算机科学中还有一个概念叫做 Fiber,英文含义就是“纤维”,意指比 Thread 更细的线,也就是比线程(Thread)控制得更精密的并发处理机制。

Fiber Root

在看源码之前,先附上 react Fiber 的神图,我也是照着图跟着函数看的
img

const ReactDOM: Object = {
    hydrate(element: React$Node, container: DOMContainer, callback: ?Function) {
    invariant(
      isValidContainer(container),
      'Target container is not a DOM element.',
    );
    if (__DEV__) {
      warningWithoutStack(
        !container._reactHasBeenPassedToCreateRootDEV,
        'You are calling ReactDOM.hydrate() on a container that was previously ' +
          'passed to ReactDOM.%s(). This is not supported. ' +
          'Did you mean to call createRoot(container, {hydrate: true}).render(element)?',
        enableStableConcurrentModeAPIs ? 'createRoot' : 'unstable_createRoot',
      );
    }
    // TODO: throw or warn if we couldn't hydrate?
    return legacyRenderSubtreeIntoContainer(
      null,
      element,
      container,
      true,
      callback,
    );
  },
  render(
    element: React$Element<any>,
    container: DOMContainer,
    callback: ?Function,
  ) {
    invariant(
      isValidContainer(container),
      'Target container is not a DOM element.',
    );
    if (__DEV__) {
      warningWithoutStack(
        !container._reactHasBeenPassedToCreateRootDEV,
        'You are calling ReactDOM.render() on a container that was previously ' +
          'passed to ReactDOM.%s(). This is not supported. ' +
          'Did you mean to call root.render(element)?',
        enableStableConcurrentModeAPIs ? 'createRoot' : 'unstable_createRoot',
      );
    }
    return legacyRenderSubtreeIntoContainer(
      null,
      element,
      container,
      false,
      callback,
    );
  },
  //此处暂时省略其他代码。。。
}

我们可以看到,hydrate 和 render 都返回了一个 legacyRenderSubtreeIntoContainer 函数,那么 legacyRenderSubtreeIntoContainer 函数做了什么

function legacyRenderSubtreeIntoContainer(
  parentComponent: ?React$Component<any, any>,
  children: ReactNodeList,
  container: DOMContainer,
  forceHydrate: boolean,
  callback: ?Function,
) {
  if (__DEV__) {
    topLevelUpdateWarnings(container);
  }

  // TODO: Without `any` type, Flow says "Property cannot be accessed on any
  // member of intersection type." Whyyyyyy.

  let root: Root = (container._reactRootContainer: any);
  // 第一次root 不存在
  if (!root) {
    // Initial mount
    // 创建reactRoot,在dom元素上挂载,
    root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
      container,
      forceHydrate,
    );
    if (typeof callback === 'function') {
      const originalCallback = callback;
      callback = function() {
        const instance = getPublicRootInstance(root._internalRoot);
        originalCallback.call(instance);
      };
    }
    // Initial mount should not be batched.
    // 首次不用批量更新, batchedUpdate是等view更新完之后在去统一更新状态, 有一个等待的过程, 第一次不需要,可以更快
    unbatchedUpdates(() => {
      if (parentComponent != null) {
        root.legacy_renderSubtreeIntoContainer(
          parentComponent,
          children,
          callback,
        );
      } else {
        // ReactRoot.prototype.render
        root.render(children, callback);
      }
    });
  } else {
    if (typeof callback === 'function') {
      const originalCallback = callback;
      callback = function() {
        const instance = getPublicRootInstance(root._internalRoot);
        originalCallback.call(instance);
      };
    }
    // Update
    if (parentComponent != null) {
      root.legacy_renderSubtreeIntoContainer(
        parentComponent,
        children,
        callback,
      );
    } else {
      // ReactRoot.prototype.render
      root.render(children, callback);
    }
  }
  return getPublicRootInstance(root._internalRoot);
}

legacyRenderSubtreeIntoContainer 主要作用:

  • 创建 FiberRoot
  • 调用了批处理函数 unbatchUpdates
  • 对节点进行标识
  • 执行完以后,得到的是 ReactSyncRoot,也就是 FiberTree

我们可以看到,在创建 Fiber Root 的时候调用了 legacyCreateRootFromDOMContainer 函数,我们看一下

function legacyCreateRootFromDOMContainer(
  container: DOMContainer,
  forceHydrate: boolean,
): Root {
  // 是否需要复用节点
  const shouldHydrate =
    forceHydrate || shouldHydrateDueToLegacyHeuristic(container);
  // First clear any existing content.
  if (!shouldHydrate) {
    let warned = false;
    let rootSibling;
    // 删除子节点
    while ((rootSibling = container.lastChild)) {
      if (__DEV__) {
        if (
          !warned &&
          rootSibling.nodeType === ELEMENT_NODE &&
          (rootSibling: any).hasAttribute(ROOT_ATTRIBUTE_NAME)
        ) {
          warned = true;
          warningWithoutStack(
            false,
            'render(): Target node has markup rendered by React, but there ' +
              'are unrelated nodes as well. This is most commonly caused by ' +
              'white-space inserted around server-rendered markup.',
          );
        }
      }
      container.removeChild(rootSibling);
    }
  }
  if (__DEV__) {
    if (shouldHydrate && !forceHydrate && !warnedAboutHydrateAPI) {
      warnedAboutHydrateAPI = true;
      lowPriorityWarning(
        false,
        'render(): Calling ReactDOM.render() to hydrate server-rendered markup ' +
          'will stop working in React v17. Replace the ReactDOM.render() call ' +
          'with ReactDOM.hydrate() if you want React to attach to the server HTML.',
      );
    }
  }
  // Legacy roots are not async by default.
  const isConcurrent = false;
  // 返回一个新创建的ReactRoot
  return new ReactRoot(container, isConcurrent, shouldHydrate);
}

legacyCreateRootFromDOMContainer 函数主要判断了是否是 ssr,清除所有子元素,返回 ReactRoot 函数,在这里我们知道了,root 更新时同步的

ReactRoot

function ReactRoot(
  container: DOMContainer,
  isConcurrent: boolean,
  hydrate: boolean,
) {
  // 创建FiberRoot
  const root = createContainer(container, isConcurrent, hydrate);
  // 把创建的节点绑在_internalRoot属性上
  this._internalRoot = root;
}

调用 createContainer 函数,将创建的节点绑定在_internalRoot 上

createContainer

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

createFiberRoot

export 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);

  let root;
  if (enableSchedulerTracing) {
    root = ({
      current: uninitializedFiber,
      containerInfo: containerInfo,
      pendingChildren: null,

      earliestPendingTime: NoWork,
      latestPendingTime: NoWork,
      earliestSuspendedTime: NoWork,
      latestSuspendedTime: NoWork,
      latestPingedTime: NoWork,

      pingCache: null,

      didError: false,

      pendingCommitExpirationTime: NoWork,
      finishedWork: null,
      timeoutHandle: noTimeout,
      context: null,
      pendingContext: null,
      hydrate,
      nextExpirationTimeToWorkOn: NoWork,
      expirationTime: NoWork,
      firstBatch: null,
      nextScheduledRoot: null,

      interactionThreadID: unstable_getThreadID(),
      memoizedInteractions: new Set(),
      pendingInteractionMap: new Map(),
    }: FiberRoot);
  } else {
    root = ({
      // 当前应用对应的Fiber对象
      current: uninitializedFiber,
      // root节点
      containerInfo: containerInfo,
      // 只有在持久更新中会用到
      pendingChildren: null,

      pingCache: null,

      earliestPendingTime: NoWork,
      latestPendingTime: NoWork,
      earliestSuspendedTime: NoWork,
      latestSuspendedTime: NoWork,
      latestPingedTime: NoWork,

      didError: false,

      pendingCommitExpirationTime: NoWork,
      // 指向当前已经完成准备工作的Fiber Tree Root, 在commit阶段处理
      finishedWork: null,
      // 在任务被挂起的时候通过setTimeout设置的返回内容,用来下一次如果有新的任务挂起时清理还没触发的timeout
      timeoutHandle: noTimeout,
      context: null,
      pendingContext: null,
      hydrate,
      nextExpirationTimeToWorkOn: NoWork,
      // 过期时间
      expirationTime: NoWork,
      firstBatch: null,
      nextScheduledRoot: null,
    }: BaseFiberRootProperties);
  }

  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);
}

我们看到,还调用了 createHostRootFiber 函数,createHostRootFiber 函数中调用了 createFiber 方法,下面看一下 createFiber 函数

const createFiber = function(
  tag: WorkTag,
  pendingProps: mixed,
  key: null | string,
  mode: TypeOfMode,
): Fiber {
  // $FlowFixMe: the shapes are exact here but Flow doesn't like constructors
  return new FiberNode(tag, pendingProps, key, mode);
};

我们发现 createFiber 函数实例化了 FiberNode

FiberNode

function FiberNode(
  tag: WorkTag,
  pendingProps: mixed,
  key: null | string,
  mode: TypeOfMode,
) {
  // Instance
  // FiberNode的类型
  this.tag = tag;
  this.key = key;
  this.elementType = null;
  // Function|String|Symbol|Number|Object
  this.type = null;
  this.stateNode = null;
  // Fiber  表示父级 FiberNode
  this.return = null;
  // 表示第一个子 FiberNode
  this.child = null;
  // 表示紧紧相邻的下一个兄弟 FiberNode
  this.sibling = null;
  this.index = 0;
  this.ref = null;
  // 表示新的props
  this.pendingProps = pendingProps;
  // 表示经过所有流程处理后的新props
  this.memoizedProps = null;
  // 更新队列,队列内放着即将要发生的变更状态,详细内容后面再讲解
  this.updateQueue = null;
  // 表示经过所有流程处理后的新state
  this.memoizedState = null;
  this.contextDependencies = null;
  this.mode = mode;
  // Effects  可以理解为通过一个字段标识n个动作
  this.effectTag = NoEffect;
  // 下一个将要处理的副作用F
  this.nextEffect = null;
  // 第一个需要处理的副作用
  this.firstEffect = null;
  // 最后一个将要处理的副作用F
  this.lastEffect = null;
  // 过期时间
  this.expirationTime = NoWork;
  this.childExpirationTime = NoWork;
  // 指向 自己另一个镜像
  this.alternate = null;
  if (enableProfilerTimer) {
    this.actualDuration = Number.NaN;
    this.actualStartTime = Number.NaN;
    this.selfBaseDuration = Number.NaN;
    this.treeBaseDuration = Number.NaN;
    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;
    this._debugHookTypes = null;
    if (!hasBadMapPolyfill && typeof Object.preventExtensions === 'function') {
      Object.preventExtensions(this);
    }
  }
}

初始化 Fiber Root 就结束了

接下来,我们再看一下上面说到的 legacyRenderSubtreeIntoContainer 里批量更新的操作

unbatchedUpdates

// 首次不用批量更新, batchedUpdate是等view更新完之后在去统一更新状态, 有一个等待的过程, 第一次不需要,可以更快
    unbatchedUpdates(() => {
      if (parentComponent != null) {
        root.legacy_renderSubtreeIntoContainer(
          parentComponent,
          children,
          callback,
        );
      } else {
        // ReactRoot.prototype.render
        root.render(children, callback);
      }
    });

我们再看 legacy_renderSubtreeIntoContainer 函数

ReactRoot.prototype.legacy_renderSubtreeIntoContainer = function(
  parentComponent: ?React$Component<any, any>,
  children: ReactNodeList,
  callback: ?() => mixed,
): Work {
  const root = this._internalRoot;
  const work = new ReactWork();
  callback = callback === undefined ? null : callback;
  if (__DEV__) {
    warnOnInvalidCallback(callback, 'render');
  }
  if (callback !== null) {
    work.then(callback);
  }
  updateContainer(children, root, parentComponent, work._onCommit);
  return work;
};

里面调用了 updateContainer 函数

updateContainer

export function updateContainer(
  element: ReactNodeList,
  container: OpaqueRoot,
  parentComponent: ?React$Component<any, any>,
  callback: ?Function,
): ExpirationTime {

  const current = container.current;
  // 通过 msToExpirationTime 得到currentTime
  const currentTime = requestCurrentTime();
  // 根据给任务分优先级,来得到不同的过期时间
  const expirationTime = computeExpirationForFiber(currentTime, current);
  return updateContainerAtExpirationTime(
    element,
    container,
    parentComponent,
    expirationTime,
    callback,
  );
}

updateContainer 函数主要就是计算 Fiber 的优先级,通过 expirationTime 得到过期时间,最后调用 updateContainerAtExpirationTime 方法

function computeExpirationForFiber(currentTime: ExpirationTime, fiber: Fiber) {
  // var ImmediatePriority = 1; 最高优先级, 直接走messageChannel 直接处理
  // var UserBlockingPriority = 2;
  // var NormalPriority = 3;  默认
  // var LowPriority = 4;
  // var IdlePriority = 5;
  // 优先级越高,ExpirationTime 超时时间越低
  const priorityLevel = getCurrentPriorityLevel();

  let expirationTime;
  if ((fiber.mode & ConcurrentMode) === NoContext) {
    // Outside of concurrent mode, updates are always synchronous.
    // 在并发模式之外,更新始终是同步的。
    expirationTime = Sync;
    // isWorking 在renderRoot或者CommitRoot
    // isCommitting CommitRoot
  } else if (isWorking && !isCommitting) {
    // 在render阶段,优先级设置为下次渲染的到期时间
    // During render phase, updates expire during as the current render.
    expirationTime = nextRenderExpirationTime;
  } else {
    // 在commit阶段,根据priorityLevel进行expirationTime更新
    switch (priorityLevel) {
      case ImmediatePriority:
        // 立即执行
        expirationTime = Sync;
        break;
      case UserBlockingPriority:
        // 因用户交互阻塞的优先级
        expirationTime = computeInteractiveExpiration(currentTime);
        break;
      case NormalPriority:
        // 一般,默认优先级, 异步执行
        // This is a normal, concurrent update
        expirationTime = computeAsyncExpiration(currentTime);
        break;
      case LowPriority:
      case IdlePriority:
        // 低优先级或空闲状态
        expirationTime = Never;
        break;
      default:
        invariant(
          false,
          'Unknown priority level. This error is likely caused by a bug in ' +
            'React. Please file an issue.',
        );
    }

    // If we're in the middle of rendering a tree, do not update at the same
    // expiration time that is already rendering.
    // 下一个fiber存在,且当前的fiber的过期时间和下一个fiber的过期时间一致
    // 把当前的fiber的过期时间减1
    // 避免在渲染树的时候同时去更新已经渲染的树
    if (nextRoot !== null && expirationTime === nextRenderExpirationTime) {
      expirationTime -= 1;
    }
  }

  // Keep track of the lowest pending interactive expiration time. This
  // allows us to synchronously flush all interactive updates
  // when needed.
  // TODO: Move this to renderer?
  // 记录下挂起的用户交互任务中expirationTime最短的一个,在需要时同步刷新所有交互式更新
  if (
    priorityLevel === UserBlockingPriority &&
    (lowestPriorityPendingInteractiveExpirationTime === NoWork ||
      expirationTime < lowestPriorityPendingInteractiveExpirationTime)
  ) {
    lowestPriorityPendingInteractiveExpirationTime = expirationTime;
  }

  return expirationTime;
}

computeExpirationForFiber 就是计算过期时间

export function updateContainerAtExpirationTime(
  element: ReactNodeList,
  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__) {
    if (ReactFiberInstrumentation.debugTool) {
      if (current.alternate === null) {
        ReactFiberInstrumentation.debugTool.onMountContainer(container);
      } else if (element === null) {
        ReactFiberInstrumentation.debugTool.onUnmountContainer(container);
      } else {
        ReactFiberInstrumentation.debugTool.onUpdateContainer(container);
      }
    }
  }

  const context = getContextForSubtree(parentComponent);
  if (container.context === null) {
    container.context = context;
  } else {
    container.pendingContext = context;
  }

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

计算完优先级后,执行 updateContainerAtExpirationTime 函数,放入 scheduleRootUpdate 函数,用于更新 container

function scheduleRootUpdate(
  current: Fiber,
  element: ReactNodeList,
  expirationTime: ExpirationTime,
  callback: ?Function,
) {
  if (__DEV__) {
    if (
      ReactCurrentFiberPhase === 'render' &&
      ReactCurrentFiberCurrent !== null &&
      !didWarnAboutNestedUpdates
    ) {
      didWarnAboutNestedUpdates = true;
      warningWithoutStack(
        false,
        'Render methods should be a pure function of props and state; ' +
          'triggering nested component updates from render is not allowed. ' +
          'If necessary, trigger nested updates in componentDidUpdate.\n\n' +
          'Check the render method of %s.',
        getComponentName(ReactCurrentFiberCurrent.type) || 'Unknown',
      );
    }
  }
  // 新建一个update
  //   expirationTime: expirationTime,
  //   tag: UpdateState,
  //   payload: null,
  //   callback: null,
  //   next: null,
  //   nextEffect: null,
  const update = createUpdate(expirationTime);
  // Caution: React DevTools currently depends on this property
  // being called "element".
  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;
  }
  // 调用schedule的回调
  flushPassiveEffects();
  // 延迟创建update quenes, 并把update 更新到update quenes中
  // update 添加到 current.updateQuene.firstUpdate|lastUpdate
  enqueueUpdate(current, update);

  scheduleWork(current, expirationTime);

  return expirationTime;
}

scheduleRootUpdate 将 update 添加到 current.updateQuene 里,并且调用 scheduleWork 方法

scheduleWork

function scheduleWork
(fiber: Fiber, expirationTime: ExpirationTime) {
  // 设置expirationTime & 返回root节点的Fiber对象
  const root = scheduleWorkToRoot(fiber, expirationTime);
  if (root === null) {
    if (__DEV__) {
      switch (fiber.tag) {
        case ClassComponent:
          warnAboutUpdateOnUnmounted(fiber, true);
          break;
        case FunctionComponent:
        case ForwardRef:
        case MemoComponent:
        case SimpleMemoComponent:
          warnAboutUpdateOnUnmounted(fiber, false);
          break;
      }
    }
    return;
  }
  // isWorking 在render和commit两个阶段都会为true
  // 新的render过期时间不是noWork
  // 之前的过期时间大于现在新的过期时间
  // 表达的含义:当前没有任务在执行,之前执行过任务,同时当前的任务比之前执行的任务过期时间要小
  if (
    !isWorking &&
    // nextRenderExpirationTime 在初始的时候是noWork, 被设置后不再是noWork
    nextRenderExpirationTime !== NoWork &&
    // ExpirationTime时间越小, 优先级更高
    expirationTime > nextRenderExpirationTime
  ) {
    // This is an interruption. (Used for performance tracking.)
    // 中断了执行 , 因为有优先级更高的任务进来了
    // isWorking=false意味着,在上一个时间片执行完之后进这个判断,
    // 有更高优先级的任务,则中断了之前任务的执行
    interruptedBy = fiber;
    // 清空之前任务的的stack
    resetStack();
  }
  // 更新最近和最早的时间
  markPendingPriorityLevel(root, expirationTime);
  // 要么没有任何任务 要么有任务但处于commitRoot阶段
  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 ||
    // ...unless this is a different root than the one we're rendering.
    nextRoot !== root
  ) {
    const rootExpirationTime = root.expirationTime;
    requestWork(root, rootExpirationTime);
  }
  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.',
    );
  }
}

判断有无优先级更高的任务,如果没有,则执行 requestWork 方法,如果有,中断任务,清空队列,执行优先级更高的任务

requestWork

function requestWork(root: FiberRoot, expirationTime: ExpirationTime) {
  // 把root添加到调度队列, 链表的形式
  addRootToSchedule(root, expirationTime);
  // 在render过程当中, 直接返回return
  // batchedUpdate 批处理  setState  isRendering=true   第二个setState
  // 不会出现频繁式的更新
  if (isRendering) {
    // Prevent reentrancy. Remaining work will be scheduled at the end of
    // the currently rendering batch.
    return;
  }

  if (isBatchingUpdates) {

    // Flush work at the end of the batch.
    // 执行unbatchedUpdates()时会设置为true
    if (isUnbatchingUpdates) {
      // ...unless we're inside unbatchedUpdates, in which case we should
      // flush it now.
      nextFlushedRoot = root;
      nextFlushedExpirationTime = Sync;
      // 立即执行更新
      performWorkOnRoot(root, Sync, false);
    }
    // 批处理 return 不执行
    return;
  }

  // TODO: Get rid of Sync and use current time?
  // 同步任务,直接执行
  if (expirationTime === Sync) {
    // 立即同步执行
    performSyncWork();
  } else {
    // 通过ExpirationTime 进行调度
    scheduleCallbackWithExpirationTime(root, expirationTime);
  }
}

requestWork 方法里主要判断任务状态,如果是渲染中或者是批处理,直接返回,否则,执行任务,通过判断任务的状态,执行不同的方法

  • 非批处理 performWorkOnRoot
  • 同步任务 performSyncWork
  • 剩余任务 scheduleCallbackWithExpirationTime
0