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

renderRoot #6

Open
lz-lee opened this issue Aug 10, 2019 · 0 comments
Open

renderRoot #6

lz-lee opened this issue Aug 10, 2019 · 0 comments

Comments

@lz-lee
Copy link
Owner

lz-lee commented Aug 10, 2019

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