React 源码解析:第三章 Fiber 架构与 FiberNode

1,102 阅读8分钟

😆 Hi,大家好,我是张玥卿云,一个面向未来编程的程序员!准备将 React 源码解析写成一系列文章分享给大家,希望大家喜欢~

一、前言

一个最简单的 React 程序往往包含三个步骤:

  1. 创建 React 元素树:
const element = React.createElement("div", {}, '');
  1. 创建 FiberRoot 节点并挂载到 DOM 容器节点上:
const root = ReactDOM.createRoot(container);
  1. 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 及其所拥有的属性:

基本属性:

属性描述
tagFiber 的类型。例如 FunctionComponent (函数组件)、ClassComponent(类组件)。
elementTypeReactElement 的类型。例如 REACT_ELEMENT_TYPE(自定义元素),REACT_PORTAL_TYPE(portal)、REACT_FRAGMENT_TYPE(Fragment)等。
typeReact.createElement 的第一个参数
stateNodechildren 对应的 ReactElement ;其他状态信息。
return父节点的引用
child子节点的引用
sibling兄弟节点的引用
indexfiber 在兄弟节点之间的位置
key判断 fiber 身份的标志
ref引用
pendingProps将传递给组件的 props
memoizedProps当前 props
updateQueue更新队列
memoizedState当前 state
dependenciescontext 相关链表
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函数组件或类组件
HostRootFiber 树的根节点
HostPortalPortal 节点
HostComponentDOM 元素
HostTextDOM 中的 Text 类型
FragmentFragment
ModeStrictMode
ContextConsumerContext.Consumer
ContextProviderContext.Provider
ForwardRefforwardRef
ProfilerProfiler
SuspenseComponentSuspense
MemoComponentReact.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调试模式
StrictLegacyModeStrictMode
StrictEffectsModeStrictMode && 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_TYPEReactElement
REACT_PORTAL_TYPEProtal
REACT_FRAGMENT_TYPEFragment
REACT_STRICT_MODE_TYPEStrictMode
REACT_PROFILER_TYPEProfiler
REACT_PROVIDER_TYPEContext.Provider
REACT_CONTEXT_TYPEContext.Consumer
REACT_FORWARD_REF_TYPEforwardRef
REACT_SUSPENSE_TYPESuspense
REACT_SUSPENSE_LIST_TYPESuspenseList
REACT_MEMO_TYPEReact.memo
REACT_LAZY_TYPEReact.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 ,它的执行流程如下:

  1. 处理 ClassComponent 并获取 tag
  2. 处理 HostComponent 并获取 tag
  3. 处理 Fragment、StrictMode、Profiler、Suspense、SuspenseList 等节点,并调用相应工厂函数返回 fiber
  4. 处理 Context、React.memo、React.lazy、forwardRef 等 api 创建的节点并获取 tag
  5. 调用 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@ ~

😈 如感兴趣,可联系本人:张玥卿云

🐼 其他