hushicai / hushicai.github.io

Blog
https://hushicai.github.io
27 stars 1 forks source link

React首次渲染过程 #43

Open hushicai opened 5 years ago

hushicai commented 5 years ago

目前React Concurrent Mode还没有就绪,本文只分析同步模式下的渲染过程。

例子

ReactDOM.render(
  <h1>Hello World!</h1>,
  document.getElementById('container')
);

示例来自react/fixtures/packaging/babel-standalone/dev.html

初始化

首先初始化一些关键数据结构。

FiberRootNode

2019-12-06-10-41-46-image

HostRoot

Fiber tree的根节点是一个tag为HostRoot的特殊Fiber。

2019-12-06-10-41-57-image

Update

首次渲染会在updateContainer方法中为HostRoot安排一个Update:

2019-12-06-10-42-24-image

Update会加入到HostRoot节点的UpdateQueue中,其中payload包含了这次要渲染的内容。

主循环

prepareFreshStack方法中,首先调用createWorkInProgress方法,从HostRoot节点创建出一个workInProgress节点,赋值给全局变量workInProgress:

image

进入主循环workLoop

function workLoopSync() {
  // Already timed out, so perform work without checking if we need to yield.
  while (workInProgress !== null) {
    workInProgress = performUnitOfWork(workInProgress);
  }
}

同步模式下,workLoop的具体实现方法为workLoopSync

主循环就一个while循环,处理当前workInProgress,返回下一个workInProgress

performUnitOfWork

function performUnitOfWork(unitOfWork: Fiber): Fiber | null {
   const current = unitOfWork.alternate;

  // ...

  let next= beginWork(current, unitOfWork, renderExpirationTime);

  if (next === null) {
    next = completeUnitOfWork(unitOfWork);
  }

  // ...

  return next;
}

beginWork

beginWork返回workInProgress.child作为下一个节点。

let next = beginWork(current, unitOfWork, renderExpirationTime);

首次渲染,beginWork走的是updateHostRoot分支:

    case HostRoot:
      return updateHostRoot(current, workInProgress, renderExpirationTime);

updateHostRoot方法首先会处理前面安排的Update任务,拿到最终的memoizedState,其中包含了当前workInProgress节点的nextChildren

const nextState = workInProgress.memoizedState;

const nextChildren = nextState.element;

在本例子中,nextChildrenh1元素:

2019-12-06-10-55-16-image

然后调用reconcileChildFibers方法构建子节点。


workInProgress.child = reconcileChildFibers(

  workInProgress,

  current.child,

  nextChildren,

  renderExpirationTime,

);

最后返回h1节点作为下一个workInProgress节点:

2019-12-16-16-59-16-image

回到主循环,再次进入beginWork,h1是一个HostComponent,走updateHostComponent分支:

    case HostComponent:
      return updateHostComponent(current, workInProgress, renderExpirationTime);

h1的子元素是纯文本,无需再构建Fiber节点:

  if (isDirectTextChild) {
    nextChildren = null;
  }

没有next节点了,进入completeUnitOfWork

completeUnitOfWork

伪代码如下:

workInProgress = unitOfWork;
do {
    let returnFiber = workInProgress.return;
    let next = completeWork(workInProgress.alternate, workInProgress, expirationTime);

    if (next !== null) {
        return next;
    }

    // ...
    // build effects list
    // ...

    if (workInProgress.sibling) {
        return workInProgress.sibling
    }

    workInProgress = returnFiber;
} while(workInProgress != null);

从以上代码可以看出,这是一个深度优先遍历过程。

beginWork沿着child遍历,每轮迭代,都以workInProgress.child节点作为下一个workInProgress节点,直至到达叶子节点,React就会完成该节点(completeUnitOfWork),创建stateNode、标记Effects等。 叶子节点的工作完成后,检查sibling,如果有sibling节点,则返回sibling节点作为下一个workInProgress节点。

如果没有sibling节点,则以return节点作为下一个workInProgress节点。

completeWork

创建stateNode:

      let instance = createInstance(
        type,
        newProps,
        rootContainerInstance,
        currentHostContext,
        workInProgress,
      );
      // ...
      // This needs to be set before we mount Flare event listeners
      workInProgress.stateNode = instance;

Effects List

从当前workInProgress节点开始,沿着return链构建Effects List,通过firstEffect、lastEffect、nextEffect等指针串联成一个Effects链表,直至workInProgress为空。

2019-12-16-18-08-10-image

提交

所有工作都完成以后,将workInProgress树挂载到FiberRootNode上:

2019-12-16-18-16-38-image

commitRoot:

遍历effects list:

  let firstEffect;
  if (finishedWork.effectTag > PerformedWork) {
    if (finishedWork.lastEffect !== null) {
      finishedWork.lastEffect.nextEffect = finishedWork;
      firstEffect = finishedWork.firstEffect;
    } else {
      firstEffect = finishedWork;
    }
  } else {
    // There is no effect on the root.
    firstEffect = finishedWork.firstEffect;
  }

  nextEffect = firstEffect;

  do {
    try {
      commitMutationEffects(root, renderPriorityLevel);
    } catch (error) {
      nextEffect = nextEffect.nextEffect;
    }
  } while (nextEffect !== null);

commitMutationEffects中,根据effectTag做不同的更新,本例首次渲染走的是commitPlacement

commitPlacement中插入dom节点,首先渲染过程完成!

小结

这个例子比较简单,分析起来相对简单一些,实际渲染过程中会有更多细节。