React16源码: React中Fiber对象的源码实现

关于 Fiber 对象

  • FiberRoot里面,它也会为我们去创建的一个对象叫做 Fiber
  • 在 React16 之后, 非常核心,非常重要的一个东西
  • A. 每个 ReactElement 都会对应一个 Fiber 对象
  • B. 它会记录节点的各种状态
    • 比如,class component,它的state和props
    • 这些东西是记录在 Fiber 对象上面的
    • 在 Fiber 更新之后才会更新到 class component 上面的 this.state, this.props 里面
    • 并不是通过 class component 自己去调理的这个过程
    • 这些过程都是在 Fiber 上面进行操作的,只是在更新了这个节点之后
    • 它才会把这个属性放到this上面,因此也给了react实现 hooks 一个方便
    • 因为hooks是用在function component里面的,里面没有this
    • 我们本身记录state还有props都不是在class component的对象上面,而是在 Fiber 上面
    • 我们有能力记录这个状态之后,也可以让function component更新的过程当中去拿到这个更新之后的state
    • 所以Fiber有一个非常重要的作用,就是记录节点的各种状态
  • C. 串联整个应用
    • 在 ReactElement 中,通过 props.children 属性把整个应用串联起来

    • 每一个节点都可以通过一个属性去拿到它的子节点

    • 在 Fiber 里面,它也是有这么一个能力来帮助我们记录整个应用的一个树形结构的一个状态,把每一个节点给它串联起来

    • 在 Fiber 里面它是如何进行一个串联的呢?需要先回顾一下 ReactElement 的串联方式, 结合前文中的举例,如下

      tree 复制代码
                  App
                  |
              render() return
                  |
                  div
                  / \
                /   \
        children[0]  children[1]
                /       \
              /         \
              /           \
          Input          List
            |               \
      render() return     render() return 
            |                 \
          input        (span  span  span  button)
    • 上面就是 ReactElement 基于 render() 函数 return 了子节点,由此构建的树形结构

    • Fiber 里面也有这个能力记录整个应用的树形结构和状态,把每个节点串联起来

      tree 复制代码
                
                   -----current-----> 
        FiberRoot                     RootFiber
                   <---stateNode-----    |   
                                         |
                                       child  
                                         |
                                         ↓
                                        App
                                         |
                                       child
                                         |
                                         ↓
                                         |
                                        div
                                        / \
                                       /   \
                                   child   child
                                     /       \
                                    /         \
                                   /           \
                                Input-sibling-> List
                                  |               \
                                child            child
                                  |                \
                                  ↓                 \
                                input               span --sibling--> span --sibling--> span --sibling--> button
    • 被首先创建的 FiberRoot 有一个 current 属性,指向一个 Fiber对象,我们叫做 RootFiber

    • 因为 FiberRoot 在 ReactDOM.render 时,接受了 一个参数是 App

    • App 这个 ReactElement 它对应的Fiber对象就是 RootFiber 的 child

    • App 的 child 就是 div 节点,因为它的 render 函数渲染出来之后就是一个 div 的唯一子节点

    • 这个 div 接收到的 props.children 是一个数组,这个数组的第一个节点会作为它的 child, 其指向 Input 组件对应的 Fiber对象

    • List 也是作为 div的 props.children 的一项,它是 Input 对应Fiber对象的 sibling 兄弟节点

    • 也就是说,每个节点只会存储它的第一个子节点,不会把它的子节点都存放在一起,通过上述单项的指向来进行存储

    • 对于 Input 组件来说,它的 child 就是其 render 函数 return 的一个 input 原生标签

    • 对于 List 组件来说,它返回的是一个数组,它的第一个子节点就是 span, 剩下的兄弟节点通过 sibling 来连接

    • 而每个子节点都会有一个 return 属性指向自己的父节点,上述图示中并没有标示出来

    • 我们进行节点的查找就会非常方便,只需要通过 child 一路向下查找,直接到叶子节点

    • 基于这种方式来遍历整个应用,即通过react的这种数据结构,把整个 Fiber树给它串联起来,提供给我们一个方便高效的遍历方式

    • 现在看下 Fiber 的属性,源码在: https://github.com/facebook/react/blob/v16.6.0/packages/react-reconciler/src/ReactFiber.js

      ts 复制代码
      // A Fiber is work on a Component that needs to be done or was done. There can
      // be more than one per component.
      export type Fiber = {|
        // These first fields are conceptually members of an Instance. This used to
        // be split into a separate type and intersected with the other Fiber fields,
        // but until Flow fixes its intersection bugs, we've merged them into a
        // single type.
      
        // An Instance is shared between all versions of a component. We can easily
        // break this out into a separate object to avoid copying so much to the
        // alternate versions of the tree. We put this on a single object for now to
        // minimize the number of objects created during the initial render.
      
        // Tag identifying the type of fiber.
        tag: WorkTag, // 标记不同的组件类型,有原生dom节点的组件, 有 class component, function component, 各种各样的组件都有对应的类型
      
        // Unique identifier of this child.
        key: null | string, // ReactElement 里面的key
      
        // The value of element.type which is used to preserve the identity during
        // reconciliation of this child.
        elementType: any, // ReactElement.type, 就是调用 createElement 的第一个参数
      
        // The resolved function/class/ associated with this fiber.
        type: any, // 异步组件 resolved 之后返回的内容, 一般是 function 或 class
      
        // The local state associated with this fiber.
        stateNode: any, // 跟当前Fiber相关的本地状态,在浏览器环境中就是DOM节点,对应节点实际的实例,比如 class component 对应 class 的实例, dom组件对应dom节点的实例,function component 没有实例,就没有 stateNode, 有了它在应用更新完成后,就可以把 最新的 state,props 等放到节点上
      
        // Conceptual aliases
        // parent : Instance -> return The parent happens to be the same as the
        // return fiber since we've merged the fiber and instance.
      
        // Remaining fields belong to Fiber
      
        // The Fiber to return to after finishing processing this one.
        // This is effectively the parent, but there can be multiple parents (two)
        // so this is only the parent of the thing we're currently processing.
        // It is conceptually the same as the return address of a stack frame.
        return: Fiber | null,
      
        // Singly Linked List Tree Structure.
        child: Fiber | null,
        sibling: Fiber | null,
        index: number,
      
        // The ref last used to attach this node.
        // I'll avoid adding an owner field for prod and model that as functions.
        ref: null | (((handle: mixed) => void) & {_stringRef: ?string}) | RefObject, // ref 属性
      
        // Input is the data coming into process this fiber. Arguments. Props.
        pendingProps: any, // This type will be more specific once we overload the tag. // 新的变动,带来的新的 props,就存在 pendingProps 属性上
        memoizedProps: any, // The props used to create the output. // 上一次更新结束之后的 props
      
        // A queue of state updates and callbacks.
        updateQueue: UpdateQueue<any> | null, // 该 Fiber 对应的组件产生的 update 会存放在这个队列里面
      
        // The state used to create the output
        memoizedState: any, // 上次渲染完成之后的 state,比如我们执行setState,会算出一个新的state,算出是要经过 updateQueue计算的, 计算完之后覆盖 memoizedState 值
      
        // A linked-list of contexts that this fiber depends on
        firstContextDependency: ContextDependency<mixed> | null, // 一个列表,存放这个Fiber依赖的context
      
        // Bitfield that describes properties about the fiber and its subtree. E.g.
        // the ConcurrentMode flag indicates whether the subtree should be async-by-
        // default. When a fiber is created, it inherits the mode of its
        // parent. Additional flags can be set at creation time, but after that the
        // value should remain unchanged throughout the fiber's lifetime, particularly
        // before its child fibers are created.
        mode: TypeOfMode, // 用于描述当前Fiber和它子树的Bitfield, 共存的模式表示这个子树是否默认是异步渲染的, Fiber 被创建的时候会继承父Fiber, 其他的标识也可以在创建的时候被设置,但在创建之后不应该再被修改,特别是他的子Fiber创建之前, 对应比如: ConcurrentMode, StrictMode,
      
        // Effect
        effectTag: SideEffectTag, // 副作用,用来记录 Side Effect
      
        // Singly linked list fast path to the next fiber with side-effects.
        nextEffect: Fiber | null, // 副作用, 单链表用来快速查找下一个 side effect
      
        // The first and last fiber with side-effect within this subtree. This allows
        // us to reuse a slice of the linked list when we reuse the work done within
        // this fiber.
        firstEffect: Fiber | null, // 副作用,子树中第一个 side effect
        lastEffect: Fiber | null, // 副作用,子树中最后一个 side effect
      
        // Represents a time in the future by which this work should be completed.
        // Does not include work found in its subtree.
        expirationTime: ExpirationTime, // 当前节点产生的更新任务的过期时间, 代表任务在未来的哪个时间点应该被完成,不包括他的子树产生的任务
      
        // This is used to quickly determine if a subtree has no pending changes.
        childExpirationTime: ExpirationTime, // 它的子节点如果产生更新,记录子节点的过期时间, 快速确定子树中是否有不在等待的变化
      
        // This is a pooled version of a Fiber. Every fiber that gets updated will
        // eventually have a pair. There are cases when we can clean up pairs to save
        // memory if we need to.
        alternate: Fiber | null, // 在Fiber树更新的过程中,每个Fiber都会有一个跟其对应的Fiber,我们称它为 current <==> workInProgress的对应关系, 在渲染完成之后他们会交换位置,在更新过程中,会根据整个Fiber对象,创建一个 workInProgress, current是当前的, workInProgress 是要进行一个更新的, 在更新完成后,workInProgress 状态会更新成一个新的,current就变成老的,最终节点渲染到 dom 上面,workInProgress 会变成current,再下一次更新到来的时候,会保持着两个对象都存在,这个在 react 中叫做 double buffer, 是用来提高性能的
      
        // Time spent rendering this Fiber and its descendants for the current update.
        // This tells us how well the tree makes use of sCU for memoization.
        // It is reset to 0 each time we render and only updated when we don't bailout.
        // This field is only set when the enableProfilerTimer flag is enabled.
        actualDuration?: number,
      
        // If the Fiber is currently active in the "render" phase,
        // This marks the time at which the work began.
        // This field is only set when the enableProfilerTimer flag is enabled.
        actualStartTime?: number,
      
        // Duration of the most recent render time for this Fiber.
        // This value is not updated when we bailout for memoization purposes.
        // This field is only set when the enableProfilerTimer flag is enabled.
        selfBaseDuration?: number,
      
        // Sum of base times for all descedents of this Fiber.
        // This value bubbles up during the "complete" phase.
        // This field is only set when the enableProfilerTimer flag is enabled.
        treeBaseDuration?: number,
      
        // Conceptual aliases
        // workInProgress : Fiber ->  alternate The alternate used for reuse happens
        // to be the same as work in progress.
        // __DEV__ only
        _debugID?: number,
        _debugSource?: Source | null,
        _debugOwner?: Fiber | null,
        _debugIsCurrentlyTiming?: boolean,
      |};
    • Fiber里面三个重要的属性: return, child, sibling 可以帮助把应用中的所有节点一一串联

      • return: 指向它在Fiber节点树中的 parent, 用来处理完这个节点之后,向上返回
      • child: 单链表树结构, 指向自己的第一个子节点
      • sibling: 指向自己的兄弟结构,兄弟节点的return指向同一个父节点
    • 上述 Effect 相关的属性,都是副作用,是用来标记 Dom 节点要进行那些更新的工具,以及标记是否要执行组件生命周期的内容

    • 上述 Duration 相关属性,是渲染时间相关的,和 React DEV TOOL 有相关关系

    • 其他属性,都在代码中备注

相关推荐
老码沉思录12 小时前
React Native 全栈开发实战班 - 图片加载与优化
javascript·react native·react.js
GISer_Jing12 小时前
React|bpmn.js|react-bpmn使用示例详解
前端·javascript·react.js
Fanfffff72013 小时前
【React 进阶】掌握 React18 全部 Hooks
前端·react.js·前端框架
默涵H13 小时前
React:前端开发的超级英雄,带你从零到一
前端·react.js
GISer_Jing19 小时前
React面试高频核心问题
前端·react.js·面试
Mrs_Lupin20 小时前
如何在react中使用react-monaco-editor渲染出一个编辑器
前端·react.js·编辑器
ZHOU_WUYI20 小时前
用 React18 构建Tic-Tac-Toe(井字棋)游戏
javascript·react.js·游戏
老码沉思录21 小时前
React Native 全栈开发实战班 -原生功能集成之相机与图片
数码相机·react native·react.js
khatung1 天前
React中事件绑定和Vue有什么区别?
前端·javascript·vue.js·vscode·react.js·前端框架
老码沉思录1 天前
React Native 全栈开发实战班 - 原生功能集成之地理位置服务
javascript·react native·react.js