React 源码探源 2 Mount 复杂的例子
接第一章React 源码探源 1 Mount.
本文中有些结论处于笔者的猜测,如有不妥之处,欢迎指出。
源码
先看一个例子:
function Dev() {
const [count, setCount] = React.useState(0);
return (<div id="div">
<button id="btn" onClick={() => {
setCount(function add(c) { return c + 1; });
setCount(function double(c) { return c * 2; });
setCount(function minus(c) { return c - 1; });
}}>click me</button>
<div>the new text is <span>{count}</span></div>
</div>);
}
ReactDOM.render(
<div>
<h1>Hello World!</h1>
<Dev />
</div>
, document.getElementById('container'));
Render 阶段
先看 Render 阶段的 fiber tree 结果,如下

如第一章所述,在第一次 commit 之前,fiberRoot 下面的 current 还是空的,所有的 fiber 节点都在 fiberRoot.current.alternate
,fiber 中的几个重要的属性如下:
-
child
: 子节点,任何 fiber 最多只有一个子节点。 -
return
: 父节点,出了HostRoot
节点以外,都有return
指向父节点。 -
sibling
: 兄弟节点,单向指向下一个兄弟节点。 -
stateNode
: 指向 fiber 对应的 DOM 节点。 -
tag
: fiber 的标记,在此例中涉及到的 tag 有以下几个类型。- 3:
HostRoot
, fiber 树中的除了 fiberRoot 以外的根结点。 - 5:
HostComponent
, 指代 DOM 树中对应可以有子节点的节点,例如div
,button
以及span
等。 - 6:
HostText
, 指代 DOM 树中的文字。 - 0:
FunctionComponent
, 函数组件,本例中只有function Dev()
。
- 3:
-
type
: fiber 的类型,如果是HostComponent
,指定对应的 DOM tag 类型,例如div
,button
,span
等。如果是FunctionComponent
,指向对应的函数定义,本例中指定function Dev()
。 -
flags
: flags 使用字节位进行存储,所以一个 flags 中可以使用多种标记。在 render 过程中, React 给 fiber 加过的标记有:- 512:
Snapshot
,从源码中可以发现,这个标记针对于组件中含有getSnapshotBeforeUpdate
生命周期的。不过对于HostRoot
,React 有单独的注释。因为是初次加载,暂时不涉及 update 的情况。// Schedule an effect to clear this container at the start of the next commit.
// This handles the case of React rendering into a container with previous children.
// It's also safe to do for updates too, because current.child would only be null
// if the previous render was null (so the the container would already be empty). - 1:
PerformedWork
, 从代码注释中可以看到,这个标记是给 React DevTools 使用的。 - 2:
Placement
,标记此 fiber 需要插入到 DOM 节点。
- 512:
另外需要注意的是, button
的 onClick
属性并没有在 fiber 树中有特殊体现,只是在 DOM 节点中有属性对其进行了存储。onClick
属性也没有对 button
添加对应的事件监听。
Commit 阶段
本例中没有 useEffect
或者 useLayoutEffect
的调用,整体的 commit
阶段比较简单。
-
commitMutationEffects
,这是主要的 commit 阶段,就是将 fiber 树 commit 到 DOM 上的阶段 -
commitMutationEffects_begin
,从代码中可以看到,这个阶段是在插入或者更新之前先做一些删除工作,咱们会在后续的系列中探索这里面的工作。 -
commitMutationEffects_complete
,可以看到这是一个递归向上的过程,本例中中只执行了一次,咱们会在后续的系列中探索这里的工作。 -
commitMutationEffectsOnFiber
,这一阶段检查 fiber(HostRoot) 下的第一个子节点fiber(HostComponent)
的 flags,发现其中有Placement
的标记。执行Placement
的操作 -
commitPlacement
,在此阶段执行插入的操作,会先检查此 fiber 是HostRoot
,找到其父节点对应的containerInfor
,即找到需要插入的地方。 -
insertOrAppendPlacementNodeIntoContainer
,最后执行此函数来将fiber(HostComponent)
下的stateNode
(div
) 插入到containerInfor
。
总结
本节通过一个例子,了解到了 ReactDOM.render
的主要流程 render
和 commit
。对 fiber 的 主要结构及主要的 commit
流程有所描述。在下一节,笔者计划再次通过此例探索 React 更新时的主要流程。本节中涉及到的过程还有很多的细节未能描述,会在后续的系列中继续探究。