React由浅入深:Fiber架构render阶段

您好,我是沧沧凉凉,是一名前端开发者,目前在掘金知乎以及个人博客上同步发表一些学习前端时遇到的趣事和知识,欢迎关注。


前文中提到了什么是Fiber架构,以及Fiber架构的工作原理,本篇文章主要来总结React框架中的render阶段,到底发生了什么事情。

首先Render阶段分为”递“阶段,以及“归”阶段,它们对应了源码中的两个方法beginWork方法completeWork方法

递阶段

首先,经过前文的总结,已经清楚了整个应用只有一个根节点:FiberRoot,而通过调用ReactDOM.render还会生成一个HostRootFiber根节点,React会从HostRootFiber节点开始向下进行深度优先遍历。

这个时候遍历到的所有Fiber节点,都会调用beginWork方法。

而该方法会根据传入的Fiber节点创建子Fiber节点,并将这两个Fiber节点连接起来。

如果当该节点没有子节点的时候,就会进入到归阶段。

至于该方法的具体内容以及效果,在后面会进行讲解。

归阶段

在“归”阶段会调用completeWork (opens new window)处理Fiber节点

当某个Fiber节点执行完completeWork,如果其存在兄弟Fiber节点(即fiber.sibling !== null),会进入其兄弟Fiber的“递”阶段。

如果不存在兄弟Fiber,会进入父级Fiber的“归”阶段。

“递”和“归”阶段会交错执行直到“归”到rootFiber。至此,render阶段的工作就结束了。

不知道各位是否还有印象,我们在介绍Fiber架构原理一文中,给出了FiberNode对象的源码,其中有这么一段:

// 用于连接其他Fiber节点形成Fiber树
this.return = null;
this.child = null;
this.sibling = null;

上面的3个属性分别代表:

  • return:指向父节点。
  • child:指向子节点。
  • sibling:指向兄弟节点。

至于为什么指向父节点的属性叫做return,我这里也就不乱猜了,反正记住在Fiber节点中,return这个属性就指向了父节点。

从这里开始,本文就要开始对于源码进行梳理总结,因为之前跟着文档过了一遍,发现很多东西都是模模糊糊的有个印象,但是理解并不深刻,通过之前的总结,这些印象逐渐变得深刻起来。

beginWork

React对于一个只有文本子节点的节点进行了一些优化,如果一个节点下只有一个文本子节点,那么该文本节点就不会生成自己的Fiber节点。

beginWork的代码量还是比较多的,大概是400多行,但其实并不仅仅是400多行的问题,它里面还调用了非常多的封装的方法,所以我们要对beginWork的源码进行逐步拆解因为beginWork中还包含了diff算法的部分。

下面的流程会逐步对源码进行分析,我会在一些比较重要的地方给上注释,注意,本次源码分析都是分析的打包后的文件react-dom.development.js,我也会给出没有打包之前的代码位置,方便各位查看源码。

每次执行beginWork只会创建一个Fiber节点。

tag对应的含义

接下来,我们看一下下面的代码:

export const FunctionComponent = 0;
export const ClassComponent = 1;
export const IndeterminateComponent = 2;
export const HostRoot = 3;
export const HostPortal = 4;
export const HostComponent = 5;
export const HostText = 6;
export const Fragment = 7;
export const Mode = 8;
export const ContextConsumer = 9;
export const ContextProvider = 10;
export const ForwardRef = 11;
export const Profiler = 12;
export const SuspenseComponent = 13;
export const MemoComponent = 14;
export const SimpleMemoComponent = 15;
export const LazyComponent = 16;
export const IncompleteClassComponent = 17;
export const DehydratedFragment = 18;
export const SuspenseListComponent = 19;
export const ScopeComponent = 21;
export const OffscreenComponent = 22;
export const LegacyHiddenComponent = 23;
export const CacheComponent = 24;
export const TracingMarkerComponent = 25;

我们可以看出来包括我们熟悉的Context、Fragment在beginWork这个方法中都会进入不同的update方法中。我们下面还是从div标签举例子,看看一个div是如何通过beginWork方法的。

挂载

completeWork


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!