Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FiberRoot和RootFiber #2

Open
lz-lee opened this issue Jul 25, 2019 · 0 comments
Open

FiberRoot和RootFiber #2

lz-lee opened this issue Jul 25, 2019 · 0 comments

Comments

@lz-lee
Copy link
Owner

lz-lee commented Jul 25, 2019

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

上一篇
下一篇

@lz-lee lz-lee changed the title React批量更新 FiberRoot和RootFiber Jul 25, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant