😆 Hi,大家好,我是张玥卿云,一个面向未来编程的程序员!准备将 React 源码解析写成一系列文章分享给大家,希望大家喜欢~
一、前言
一个最简单的 React 程序往往包含三个步骤:
- 创建 React 元素树:
const element = React.createElement("div", {}, '');
- 创建 FiberRoot 节点并挂载到 DOM 容器节点上:
const root = ReactDOM.createRoot(container);
- FiberRoot 渲染第一步创建的 React 元素树:
root.render(element);
前两个章节我们介绍了 ReactElement 与 FiberRoot 的相关内容,今天我们将要开始进入第三个步骤:元素树的渲染。元素树的渲染是 React 框架的重点,它包含 Fiber 架构的实现(虚拟 dom)和对纤程的实现。这个章节我们主要介绍 Fiber 架构的一些前置知识点:“Fiber 架构与 FiberNode”。
二、认识 Fiber 架构
2.1 纤程(Fiber)
Fiber 纤程用于在单线程的环境内执行多线程的任务(即用户态的多线程实现)。React Fiber 架构实现了纤程的思想,它把虚拟 DOM 的更新任务划分为细粒度的 Fiber 更新任务,由调度器调度执行。这种执行机制让虚拟 DOM 的更新和浏览器的渲染可以并发执行,避免了浏览器渲染被阻塞的情况,并且实现了更新任务的中断和重启。
2.2 Fiber 架构的实现
Fiber 架构的中心数据结构是 Fiber 树(树形数据结构,对应 DOM 树)。在源码中,这棵树的节点含有对父节点、孩子节点、兄弟节点的引用,实现了从树的遍历到链表的访问的转化。并且为了重复利用 Fiber 节点,React 创建了两颗 Fiber 树(current 树和 workInProgress 树)交替工作。这些之后的章节将会详细介绍,今天我们的主要内容为 Fiber 树的节点:FiberNode。
三、Fiber 树的节点
3.1 FiberNode
FiberNode 是 Fiber 节点的类实现,其内部包含了基本属性、树(链表)节点的引用属性、内部逻辑所需的属性等。以下内容将详细介绍 FiberNode 及其所拥有的属性:
基本属性:
| 属性 | 描述 |
|---|---|
| tag | Fiber 的类型。例如 FunctionComponent (函数组件)、ClassComponent(类组件)。 |
| elementType | ReactElement 的类型。例如 REACT_ELEMENT_TYPE(自定义元素),REACT_PORTAL_TYPE(portal)、REACT_FRAGMENT_TYPE(Fragment)等。 |
| type | React.createElement 的第一个参数 |
| stateNode | children 对应的 ReactElement ;其他状态信息。 |
| return | 父节点的引用 |
| child | 子节点的引用 |
| sibling | 兄弟节点的引用 |
| index | fiber 在兄弟节点之间的位置 |
| key | 判断 fiber 身份的标志 |
| ref | 引用 |
| pendingProps | 将传递给组件的 props |
| memoizedProps | 当前 props |
| updateQueue | 更新队列 |
| memoizedState | 当前 state |
| dependencies | context 相关链表 |
| mode | 模式 |
除了上面列出的属性,FiberNode 还有标志、优先级、性能测量等其他类型的属性,这里不过多讲解了,源码注释如下:
function FiberNode(
tag,
pendingProps,
key,
mode,
) {
/** 类型 */
this.tag = tag;
/** 键值 */
this.key = key;
/** ReactElement 的类型 */
this.elementType = null;
/** React.createElement 的第一个参数 */
this.type = null;
/** children 的 ReactElement 或其他状态信息。 */
this.stateNode = null;
/** 父节点引用 */
this.return = null;
/** 第一个子节点的引用 */
this.child = null;
/** 兄弟节点的引用 */
this.sibling = null;
/** 在兄弟节点中的位置 */
this.index = 0;
/** 引用 */
this.ref = null;
/** 将传递给组件的 props */
this.pendingProps = pendingProps;
/** 当前 props */
this.memoizedProps = null;
/** 更新队列 */
this.updateQueue = null;
/** 当前 state */
this.memoizedState = null;
/** context 相关链表 */
this.dependencies = null;
/** 模式 */
this.mode = mode;
/** React 工作流程中的一些标志位 */
this.flags = NoFlags;
this.subtreeFlags = NoFlags;
this.deletions = null;
/** 优先级相关的属性 */
this.lanes = NoLanes;
this.childLanes = NoLanes;
/** current 树的节点和 alternate 树的节点相互引用的属性 */
this.alternate = null;
/** 测量性能时使用到的一些属性 */
if (enableProfilerTimer) {
this.actualDuration = Number.NaN;
this.actualStartTime = Number.NaN;
this.selfBaseDuration = Number.NaN;
this.treeBaseDuration = Number.NaN;
this.actualDuration = 0;
this.actualStartTime = -1;
this.selfBaseDuration = 0;
this.treeBaseDuration = 0;
}
}
3.2 Fiber 的类别 (fiber.tag)
上面罗列了 FiberNode 的属性,现在具体来看 tag 属性。tag 属性代表 FiberNode 的类别,React 对每类 FiberNode 采取不同的处理方式,例如类组件和函数组件在 React 源码内的处理方式就是很不相同的,这个以后我们会详细讲解。现在我们先简单了解下 Fiber 节点都有哪些类别:
| 类别 | 描述 |
|---|---|
| FunctionComponent | 函数组件 |
| ClassComponent | 类组件 |
| IndeterminateComponent | 函数组件或类组件 |
| HostRoot | Fiber 树的根节点 |
| HostPortal | Portal 节点 |
| HostComponent | DOM 元素 |
| HostText | DOM 中的 Text 类型 |
| Fragment | Fragment |
| Mode | StrictMode |
| ContextConsumer | Context.Consumer |
| ContextProvider | Context.Provider |
| ForwardRef | forwardRef |
| Profiler | Profiler |
| SuspenseComponent | Suspense |
| MemoComponent | React.memo |
源代码如下:
/** 函数组件 */
export const FunctionComponent = 0;
/** 类组件 */
export const ClassComponent = 1;
/** 函数组件或类组件 */
export const IndeterminateComponent = 2;
/** Fiber 树的根节点 */
export const HostRoot = 3;
/** Portal 节点 */
export const HostPortal = 4;
/** DOM 元素 */
export const HostComponent = 5;
/** DOM 中的 Text 类型 */
export const HostText = 6;
/** Fragment */
export const Fragment = 7;
/** StrictMode */
export const Mode = 8;
/** Context.Consumer */
export const ContextConsumer = 9;
/** Context.Provider */
export const ContextProvider = 10;
/** forwardRef */
export const ForwardRef = 11;
/** Profiler */
export const Profiler = 12;
/** Suspense */
export const SuspenseComponent = 13;
/** React.memo */
export const MemoComponent = 14;
/** React.memo */
export const SimpleMemoComponent = 15;
/** React.lazy */
export const LazyComponent = 16;
/** 未完成的类组件 */
export const IncompleteClassComponent = 17;
/** 未完成的类组件 */
export const DehydratedFragment = 18;
/** SuspenseList */
export const SuspenseListComponent = 19;
/** React Scope */
export const ScopeComponent = 21;
/** 离屏组件 */
export const OffscreenComponent = 22;
// 以下类型日后研究
//
// /** 还没看这部分代码 */
// export const LegacyHiddenComponent = 23;
// /** 还没看这部分代码 */
// export const CacheComponent = 24;
// /** 还没看这部分代码 */
// export const TracingMarkerComponent = 25;
3.3 Fiber 的工作模式 (fiber.mode)
Fiber 的工作模式决定了 React 采用什么策略去处理 Fiber 节点。例如 Concurrent 模式采用了时间分片的并发模式、Profile 模式采用了性能分析模式、DebugTracing 开启了调试模式等,这些模式是可以同时存在的。
| 模式 | 描述 |
|---|---|
| NoMode | 无模式 |
| ConcurrentMode | 并发模式 |
| ProfileMode | 性能分析模式 |
| DebugTracingMode | 调试模式 |
| StrictLegacyMode | StrictMode |
| StrictEffectsMode | StrictMode && enableStrictEffects |
| ConcurrentUpdatesByDefaultMode | 并发模式下 lane 使用被赋予的 lane 模式 |
/** 无模式 */
export const NoMode = /* */ 0b000000;
/** 并发模式 */
export const ConcurrentMode = /* */ 0b000001;
/** 性能分析模式 */
export const ProfileMode = /* */ 0b000010;
/** 调试模式 */
export const DebugTracingMode = /* */ 0b000100;
/** StrictMode */
export const StrictLegacyMode = /* */ 0b001000;
/** StrictMode && enableStrictEffects */
export const StrictEffectsMode = /* */ 0b010000;
/** 并发模式下 lane 使用被赋予的 lane 模式 */
export const ConcurrentUpdatesByDefaultMode = /* */ 0b100000;
3.4 ReactElement 的类型(fiber.elementType)
elementType 属性则代表了 ReactElement 的类型,和我们平时使用的 jsx 元素相对应,列出的表格如下:
| 元素类型 | 描述 |
|---|---|
| REACT_ELEMENT_TYPE | ReactElement |
| REACT_PORTAL_TYPE | Protal |
| REACT_FRAGMENT_TYPE | Fragment |
| REACT_STRICT_MODE_TYPE | StrictMode |
| REACT_PROFILER_TYPE | Profiler |
| REACT_PROVIDER_TYPE | Context.Provider |
| REACT_CONTEXT_TYPE | Context.Consumer |
| REACT_FORWARD_REF_TYPE | forwardRef |
| REACT_SUSPENSE_TYPE | Suspense |
| REACT_SUSPENSE_LIST_TYPE | SuspenseList |
| REACT_MEMO_TYPE | React.memo |
| REACT_LAZY_TYPE | React.lazy |
源码如下:
/** ReactElement */
export const REACT_ELEMENT_TYPE = Symbol.for('react.element');
/** Protal */
export const REACT_PORTAL_TYPE = Symbol.for('react.portal');
/** Fragment */
export const REACT_FRAGMENT_TYPE = Symbol.for('react.fragment');
/** StrictMode */
export const REACT_STRICT_MODE_TYPE = Symbol.for('react.strict_mode');
/** Profiler */
export const REACT_PROFILER_TYPE = Symbol.for('react.profiler');
/** Context.Provider */
export const REACT_PROVIDER_TYPE = Symbol.for('react.provider');
/** Context.Consumer */
export const REACT_CONTEXT_TYPE = Symbol.for('react.context');
export const REACT_SERVER_CONTEXT_TYPE = Symbol.for(
'react.server_context',
);
/** forwardRef */
export const REACT_FORWARD_REF_TYPE = Symbol.for('react.forward_ref');
/** Suspense */
export const REACT_SUSPENSE_TYPE = Symbol.for('react.suspense');
/** SuspenseList */
export const REACT_SUSPENSE_LIST_TYPE = Symbol.for(
'react.suspense_list',
);
/** React.memo */
export const REACT_MEMO_TYPE = Symbol.for('react.memo');
/** React.lazy */
export const REACT_LAZY_TYPE = Symbol.for('react.lazy');
/** React Scope */
export const REACT_SCOPE_TYPE = Symbol.for('react.scope');
/** 调试模式 */
export const REACT_DEBUG_TRACING_MODE_TYPE = Symbol.for(
'react.debug_trace_mode',
);
/** 离屏组件 */
export const REACT_OFFSCREEN_TYPE = Symbol.for('react.offscreen');
// 以下类型日后研究
//
// export const REACT_LEGACY_HIDDEN_TYPE = Symbol.for(
// 'react.legacy_hidden',
// );
// export const REACT_CACHE_TYPE = Symbol.for('react.cache');
// export const REACT_TRACING_MARKER_TYPE = Symbol.for(
// 'react.tracing_marker',
// );
// export const REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED = Symbol.for(
// 'react.default_value',
// );
// export const REACT_MEMO_CACHE_SENTINEL = Symbol.for(
// 'react.memo_cache_sentinel',
// );
四、FiberNode 的创建
上面内容简略介绍了 FiberNode 类,现在我们来看如何创建 FiberNode,即 Fiber Node 的工厂函数。createFiber 是 FiberNode 最直接的工厂函数,所有 Fiber 的创建都要通过它来完成:
const createFiber = function(
tag,
pendingProps,
key,
mode,
) {
/** 返回 FiberNode 实例 */
return new FiberNode(tag, pendingProps, key, mode);
};
在 create Fiber 之上还有一些创建特定类别 fiber 的工厂函数,例如 createFiberFromElement、createHostRootFiber、createFiberFromProfiler 等,每个工厂函数会设置该类 Fiber 所对应的各种属性(例如 elementType 等)。本章我们将用创建 ReactElement 对应的 Fiber 的工厂函数作为例子来讲解。
4.1 创建 ReactElement 对应的 Fiber
createFiberFromElement 是创建 ReactElement 所对应的 Fiber 的工厂函数。它调用了另外一个负函数来创建 fiber(createFiberFromTypeAndProps)。
export function createFiberFromElement(
element,
mode,
lanes,
) {
let owner = null;
const type = element.type;
const key = element.key;
const pendingProps = element.props;
/** 调用 createFiberFromTypeAndProps 创建 fiber */
const fiber = createFiberFromTypeAndProps(
type,
key,
pendingProps,
owner,
mode,
lanes,
);
return fiber;
}
createFiberFromTypeAndProps 会根据不同的 type 来为不同的 ReactElement 创建 fiber ,它的执行流程如下:
- 处理 ClassComponent 并获取 tag
- 处理 HostComponent 并获取 tag
- 处理 Fragment、StrictMode、Profiler、Suspense、SuspenseList 等节点,并调用相应工厂函数返回 fiber
- 处理 Context、React.memo、React.lazy、forwardRef 等 api 创建的节点并获取 tag
- 调用 createFiber 工厂函数创建 fiber 并返回
源码解析如下:
export function createFiberFromTypeAndProps(
type,
key,
pendingProps,
owner,
mode,
lanes,
) {
/** 设置 tag 为 IndeterminateComponent */
let fiberTag = IndeterminateComponent;
let resolvedType = type;
/** 判断是否为类组件,若为类组件,则设置 tag 为 ClassComponent */
if (typeof type === 'function') {
if (shouldConstruct(type)) {
fiberTag = ClassComponent;
}
/** 判断是否为 DOM 元素对应的 ReactElement,若是则设置 tag 为 HostComponent */
} else if (typeof type === 'string') {
fiberTag = HostComponent;
} else {
getTag: switch (type) {
/** 处理 Fragment 节点 */
case REACT_FRAGMENT_TYPE:
return createFiberFromFragment(pendingProps.children, mode, lanes, key);
/** 处理 StrictMode 节点 */
case REACT_STRICT_MODE_TYPE:
fiberTag = Mode;
mode |= StrictLegacyMode;
if (enableStrictEffects && (mode & ConcurrentMode) !== NoMode) {
// Strict effects should never run on legacy roots
mode |= StrictEffectsMode;
}
break;
/** 处理 Profiler 节点 */
case REACT_PROFILER_TYPE:
return createFiberFromProfiler(pendingProps, mode, lanes, key);
/** 处理 Suspense 节点 */
case REACT_SUSPENSE_TYPE:
return createFiberFromSuspense(pendingProps, mode, lanes, key);
/** 处理 SuspenseList 节点 */
case REACT_SUSPENSE_LIST_TYPE:
return createFiberFromSuspenseList(pendingProps, mode, lanes, key);
/** 处理离屏组件 */
case REACT_OFFSCREEN_TYPE:
return createFiberFromOffscreen(pendingProps, mode, lanes, key);
case REACT_LEGACY_HIDDEN_TYPE:
if (enableLegacyHidden) {
return createFiberFromLegacyHidden(pendingProps, mode, lanes, key);
}
case REACT_SCOPE_TYPE:
if (enableScopeAPI) {
return createFiberFromScope(type, pendingProps, mode, lanes, key);
}
case REACT_CACHE_TYPE:
if (enableCache) {
return createFiberFromCache(pendingProps, mode, lanes, key);
}
case REACT_TRACING_MARKER_TYPE:
if (enableTransitionTracing) {
return createFiberFromTracingMarker(pendingProps, mode, lanes, key);
}
case REACT_DEBUG_TRACING_MODE_TYPE:
if (enableDebugTracing) {
fiberTag = Mode;
mode |= DebugTracingMode;
break;
}
// eslint-disable-next-line no-fallthrough
default: {
/** 处理自定义组件 */
if (typeof type === 'object' && type !== null) {
switch (type.$$typeof) {
/** 处理 Context.Provider */
case REACT_PROVIDER_TYPE:
fiberTag = ContextProvider;
break getTag;
/** 处理 Context.Consumer */
case REACT_CONTEXT_TYPE:
fiberTag = ContextConsumer;
break getTag;
/** 处理 forwardRef */
case REACT_FORWARD_REF_TYPE:
fiberTag = ForwardRef;
break getTag;
/** 处理 React.memo */
case REACT_MEMO_TYPE:
fiberTag = MemoComponent;
break getTag;
/** 处理 React.lazy */
case REACT_LAZY_TYPE:
fiberTag = LazyComponent;
resolvedType = null;
break getTag;
}
}
let info = '';
throw new Error(
'Element type is invalid: expected a string (for built-in ' +
'components) or a class/function (for composite components) ' +
`but got: ${type == null ? type : typeof type}.${info}`,
);
}
}
}
/** 创建 fiber */
const fiber = createFiber(fiberTag, pendingProps, key, mode);
fiber.elementType = type;
fiber.type = resolvedType;
fiber.lanes = lanes;
return fiber;
}
五、结尾
这些就是 FiberNode 即其创建的全过程啦! 欢迎小伙伴们一起讨论~ @V@ ~
😈 如感兴趣,可联系本人:张玥卿云
- 电话: 16602927079
- 邮箱: zhangyueqingyun@foxmail.com
- 微信: abcde-ovo-yz