lz-lee / React-Source-Code

React源码阅读笔记
11 stars 0 forks source link

FiberRoot和RootFiber #2

Open lz-lee opened 5 years ago

lz-lee commented 5 years ago

FiberRoot 与 RootFiber

FiberRoot

FiberRoot对象

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 对应的树结构

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

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 的参数不同。

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

上一篇 下一篇