引言
书接上回,上一篇文章主要是介绍了 React 初次渲染过程中在内存中生成 workInProgress树的过程。主要是经历了两个阶段,一个是 beginWork,另一个是 completeWork。那React 是如何将已经生成的 WorkInProgress 树渲染到宿主环境中的呢?接下来说一说 commit 阶段,也就是将 workInProgress 树渲染到页面上的过程。
原理
demo
因为 commit 阶段涉及到副作用的执行,比如 useLayoutEffect 等。 所以我们改造一下调试源码用的组件代码。
jsx
import React, { useEffect, useState } from "react"
const Index = ()=>{
debugger;
const [state,setState] = useState(0)
useEffect(()=>{
console.log(123)
},[])
console.log('渲染吗??')
const [demo,setDemo] = useState('demo')
return <div>
{state}
<button onClick={()=>{
// debugger
// setState(1)
// setState(10)
// setState(100)
setState(0)
}}>触发渲染</button>
</div>
}
export default Index
我们的组件大概是这个样子的。下面我们来看一下 React 整个过程是怎么样的。
过程
首先看一下当走完beginWork 和completeWork 之后,生成的树是怎么样的。
可以看到,最后是生成了一个这样的树,我们的 wip 树保存在finishedWork 中,可以打印出来看一下
进入 commit 阶段的入口函数是 commitRoot
js
function commitRoot(root) {
// TODO: This no longer makes any sense. We already wrap the mutation and
// layout phases. Should be able to remove.
var previousUpdateLanePriority = getCurrentUpdatePriority();
var prevTransition = ReactCurrentBatchConfig$3.transition;
try {
ReactCurrentBatchConfig$3.transition = 0;
setCurrentUpdatePriority(DiscreteEventPriority);
commitRootImpl(root, previousUpdateLanePriority);
} finally {
ReactCurrentBatchConfig$3.transition = prevTransition;
setCurrentUpdatePriority(previousUpdateLanePriority);
}
return null;
}
下面从这个函数出发具体来看看 React 做了什么
commitRoot
commitRoot 主要的逻辑就是调用了一个commitRootImpl,同时传入了 root 树和previousUpdateLanePriority。
commitRootImpl
commitRootImpl 的作用就大了许多,我们分别来看commitRootImpl 做了哪些事情。
js
function commitRootImpl(root, renderPriorityLevel) {
do {
// 清空副作用列表
flushPassiveEffects();
} while (rootWithPendingPassiveEffects !== null);
flushRenderPhaseStrictModeWarningsInDEV();
// 判断当前 fiber 节点的子孙节点是否有副作用
var subtreeHasEffects = (finishedWork.subtreeFlags & (BeforeMutationMask | MutationMask | LayoutMask | PassiveMask)) !== NoFlags;
// 判断当前节点是否有副作用
var rootHasEffect = (finishedWork.flags & (BeforeMutationMask | MutationMask | LayoutMask | PassiveMask)) !== NoFlags;
if (subtreeHasEffects || rootHasEffect) {
var shouldFireAfterActiveInstanceBlur = commitBeforeMutationEffects(root, finishedWork);
commitMutationEffects(root, finishedWork, lanes);
commitLayoutEffects(finishedWork, root, lanes);
{
markLayoutEffectsStopped();
}
// opportunity to paint.
requestPaint();
return null;
}
从源码可以看到,commitRootImpl 主要做了三件事情
- 通过一个while 循环去调用flushPassiveEffects去清除副作用,防止因为有没有运行的副作用影响 commit过程。
- 判断子孙节点以及自己是否有副作用
- 如果有副作用,依次执行commitBeforeMutationEffects,commitMutationEffects,commitLayoutEffects,requestPaint
- 如果没有副作用,直接切树
- 其他一些收尾工作。
下面我们依次进行说明
判断副作用
在completeWork 阶段,React 将子组件的 flags 都冒泡到了父组件的subtreeFlags 中,所以可以通过subtreeFlags来检查子组件是否有副作用已经自己本身是否有副作用。
js
var subtreeHasEffects = (finishedWork.subtreeFlags & (BeforeMutationMask | MutationMask | LayoutMask | PassiveMask)) !== NoFlags;
var rootHasEffect = (finishedWork.flags & (BeforeMutationMask | MutationMask | LayoutMask | PassiveMask)) !== NoFlags;
如果有副作用的话,就依次运行commitBeforeMutationEffects ,commitMutationEffects,commitLayoutEffects,requestPaint。下面分别进行说明。 如果友友们对位运算不太熟悉,可以自行百度了解一下~
commitBeforeMutationEffects
js
function commitBeforeMutationEffects(root, firstChild) {
focusedInstanceHandle = prepareForCommit(root.containerInfo);
nextEffect = firstChild;
commitBeforeMutationEffects_begin(); // We no longer need to track the active instance fiber
var shouldFire = shouldFireAfterActiveInstanceBlur;
shouldFireAfterActiveInstanceBlur = false;
focusedInstanceHandle = null;
return shouldFire;
}
主要流程是commitBeforeMutationEffects_begin处理的,我们直接进入commitBeforeMutationEffects_begin查看 React 做了什么。
commitBeforeMutationEffects_begin
js
function commitBeforeMutationEffects_begin() {
while (nextEffect !== null) {
var fiber = nextEffect; // This phase is only used for beforeActiveInstanceBlur.
var child = fiber.child;
if ((fiber.subtreeFlags & BeforeMutationMask) !== NoFlags && child !== null) {
ensureCorrectReturnPointer(child, fiber);
nextEffect = child;
} else {
commitBeforeMutationEffects_complete();
}
}
}
主要逻辑在commitBeforeMutationEffects_complete中
commitBeforeMutationEffects_complete
js
function commitBeforeMutationEffects_complete() {
while (nextEffect !== null) {
var fiber = nextEffect;
setCurrentFiber(fiber);
try {
commitBeforeMutationEffectsOnFiber(fiber);
} catch (error) {
reportUncaughtErrorInDEV(error);
captureCommitPhaseError(fiber, fiber.return, error);
}
resetCurrentFiber();
var sibling = fiber.sibling;
if (sibling !== null) {
ensureCorrectReturnPointer(sibling, fiber.return);
nextEffect = sibling;
return;
}
nextEffect = fiber.return;
}
}
这里解释一下,这个循环的作用主要是运行了commitBeforeMutationEffectsOnFiber,同时如果当前 fiber 节点有sibling节点的话,也会运行commitBeforeMutationEffects_complete。整个逻辑是一个从下到上的过程。
commitBeforeMutationEffectsOnFiber
js
function commitBeforeMutationEffectsOnFiber(finishedWork) {
var current = finishedWork.alternate;
var flags = finishedWork.flags;
if ((flags & Snapshot) !== NoFlags) {
setCurrentFiber(finishedWork);
switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent:
{
break;
}
case ClassComponent:
{
if (current !== null) {
var prevProps = current.memoizedProps;
var prevState = current.memoizedState;
var instance = finishedWork.stateNode; // We could update instance props and state here,
// but instead we rely on them being set during last render.
// TODO: revisit this when we implement resuming.
{
if (finishedWork.type === finishedWork.elementType && !didWarnAboutReassigningProps) {
if (instance.props !== finishedWork.memoizedProps) {
error('Expected %s props to match memoized props before ' + 'getSnapshotBeforeUpdate. ' + 'This might either be because of a bug in React, or because ' + 'a component reassigns its own `this.props`. ' + 'Please file an issue.', getComponentNameFromFiber(finishedWork) || 'instance');
}
if (instance.state !== finishedWork.memoizedState) {
error('Expected %s state to match memoized state before ' + 'getSnapshotBeforeUpdate. ' + 'This might either be because of a bug in React, or because ' + 'a component reassigns its own `this.state`. ' + 'Please file an issue.', getComponentNameFromFiber(finishedWork) || 'instance');
}
}
}
var snapshot = instance.getSnapshotBeforeUpdate(finishedWork.elementType === finishedWork.type ? prevProps : resolveDefaultProps(finishedWork.type, prevProps), prevState);
{
var didWarnSet = didWarnAboutUndefinedSnapshotBeforeUpdate;
if (snapshot === undefined && !didWarnSet.has(finishedWork.type)) {
didWarnSet.add(finishedWork.type);
error('%s.getSnapshotBeforeUpdate(): A snapshot value (or null) ' + 'must be returned. You have returned undefined.', getComponentNameFromFiber(finishedWork));
}
}
instance.__reactInternalSnapshotBeforeUpdate = snapshot;
}
break;
}
case HostRoot:
{
{
var root = finishedWork.stateNode;
clearContainer(root.containerInfo);
}
break;
}
case HostComponent:
case HostText:
case HostPortal:
case IncompleteClassComponent:
// Nothing to do for these component types
break;
default:
{
throw new Error('This unit of work tag should not have side-effects. This error is ' + 'likely caused by a bug in React. Please file an issue.');
}
}
resetCurrentFiber();
}
}
这里判断当前 fiber 是否有Snapshot副作用,如果有副作用的话,根据 tag 进入不同的 case,主要有作用的 case 就两个,其余都是凑数的,没什么实际用途。
js
case ClassComponent:
{
if (current !== null) {
var prevProps = current.memoizedProps;
var prevState = current.memoizedState;
var instance = finishedWork.stateNode; // We could update instance props and state here,
// but instead we rely on them being set during last render.
// TODO: revisit this when we implement resuming.
{
if (finishedWork.type === finishedWork.elementType && !didWarnAboutReassigningProps) {
if (instance.props !== finishedWork.memoizedProps) {
error('Expected %s props to match memoized props before ' + 'getSnapshotBeforeUpdate. ' + 'This might either be because of a bug in React, or because ' + 'a component reassigns its own `this.props`. ' + 'Please file an issue.', getComponentNameFromFiber(finishedWork) || 'instance');
}
if (instance.state !== finishedWork.memoizedState) {
error('Expected %s state to match memoized state before ' + 'getSnapshotBeforeUpdate. ' + 'This might either be because of a bug in React, or because ' + 'a component reassigns its own `this.state`. ' + 'Please file an issue.', getComponentNameFromFiber(finishedWork) || 'instance');
}
}
}
var snapshot = instance.getSnapshotBeforeUpdate(finishedWork.elementType === finishedWork.type ? prevProps : resolveDefaultProps(finishedWork.type, prevProps), prevState);
{
var didWarnSet = didWarnAboutUndefinedSnapshotBeforeUpdate;
if (snapshot === undefined && !didWarnSet.has(finishedWork.type)) {
didWarnSet.add(finishedWork.type);
error('%s.getSnapshotBeforeUpdate(): A snapshot value (or null) ' + 'must be returned. You have returned undefined.', getComponentNameFromFiber(finishedWork));
}
}
instance.__reactInternalSnapshotBeforeUpdate = snapshot;
}
break;
}
case HostRoot:
{
{
var root = finishedWork.stateNode;
clearContainer(root.containerInfo);
}
break;
}
如果是一个类组件的话,会执行getSnapshotBeforeUpdate这个函数来做一些事情。因为对类组件了解不够深,这里跳过说明。 如果是HostRoot,会清除节点内容。 具体可看下面代码
js
function clearContainer(container) {
if (container.nodeType === ELEMENT_NODE) {
container.textContent = '';
} else if (container.nodeType === DOCUMENT_NODE) {
var body = container.body;
if (body != null) {
body.textContent = '';
}
}
}
这里是为了防止本身 html 节点中有内容,影响我们 React 的整个渲染。 下面我们用图来看一下整个执行过程
commitMutationEffects
和上面的流程差不多,看代码
js
function commitMutationEffects(root, firstChild, committedLanes) {
inProgressLanes = committedLanes;
inProgressRoot = root;
nextEffect = firstChild;
commitMutationEffects_begin(root);
inProgressLanes = null;
inProgressRoot = null;
}
依旧是commitMutationEffects_begin来进行一些逻辑的处理。
commitMutationEffects_begin
js
function commitMutationEffects_begin(root) {
while (nextEffect !== null) {
var fiber = nextEffect; // TODO: Should wrap this in flags check, too, as optimization
var deletions = fiber.deletions;
if (deletions !== null) {
for (var i = 0; i < deletions.length; i++) {
var childToDelete = deletions[i];
try {
commitDeletion(root, childToDelete, fiber);
} catch (error) {
reportUncaughtErrorInDEV(error);
captureCommitPhaseError(childToDelete, fiber, error);
}
}
}
var child = fiber.child;
if ((fiber.subtreeFlags & MutationMask) !== NoFlags && child !== null) {
ensureCorrectReturnPointer(child, fiber);
nextEffect = child;
} else {
commitMutationEffects_complete(root);
}
}
}
该函数主要做了两件事:
- 如果存在deletions ,先处理这部分的逻辑。
- 然后进入commitMutationEffects_complete。