React:Fiber 树的结构【面试真题】

254 阅读3分钟

面试官:说一下 Fiber 树的结构是怎么样的,例如这样的组件结构:

<A>
    <B>
  	  <C>
  	  </C>
    </B>
    <D></D>
</A>
  • 在当前(202408)的 reac-reconciler 中,创建一个 FiberNode 还是用的构造函数的方式,而不是直接使用对象。(这里有个 7 年前的 issue #9369,出于性能方面的考虑把直接用对象改成了构造函数)
    // [ReactFiber.js]
    const createFiber = enableObjectFiber
    ? createFiberImplObject
    : createFiberImplClass;
    
    // [ReactFeatureFlags.js]
    /**
    * Switches Fiber creation to a simple object instead of a constructor.
    */
    export const enableObjectFiber = false;
    
  • 让我们来看看 FiberNode 的创建函数都做了哪些工作:
    function FiberNode(
      this: $FlowFixMe,
      tag: WorkTag,
      pendingProps: mixed,
      key: null | string,
      mode: TypeOfMode,
    ) {
      // Instance
      // 和 FiberNode 实例自身相关的属性
      this.tag = tag;
      this.key = key;
      this.elementType = null;
      this.type = null;
      this.stateNode = null;
          
      // Fiber
      // 和 Fiber 树结构相关的属性
      this.return = null;
      this.child = null;
      this.sibling = null;
      this.index = 0;
      
      // 处理 ref 相关的工作 
      this.ref = null;
      this.refCleanup = null;
    
      // FiberNode 的更新前后需要使用到的属性
      this.pendingProps = pendingProps;
      this.memoizedProps = null;
      this.updateQueue = null;
      this.memoizedState = null;
      this.dependencies = null;
    
      this.mode = mode;
    
      // Effects
      // DOM 更新使用到的属性
      this.flags = NoFlags;
      this.subtreeFlags = NoFlags;
      this.deletions = null;
    
      // 优先级调度相关的属性
      this.lanes = NoLanes;
      this.childLanes = NoLanes;
    
      // current Node 和 WIP Node 相互指向对方的指针
      this.alternate = null;
    
      if (enableProfilerTimer) {
        // 这里面处理了与 V8 引擎在处理不同类型(例如整数和浮点数)的优化方式有关一个问题,略
      }
    
      if (__DEV__) {
        // 这里面处理了开发环境下的一些变量赋值和属性设置,略
      }
    }
    
    // ......
    
    // This is a constructor function, rather than a POJO constructor, still
    // please ensure we do the following:
    // 1) Nobody should add any instance methods on this. Instance methods can be
    // more difficult to predict when they get optimized and they are almost
    // never inlined properly in static compilers.
    // 2) Nobody should rely on `instanceof Fiber` for type testing. We should
    // always know when it is a fiber.
    // 3) We might want to experiment with using numeric keys since they are easier
    // to optimize in a non-JIT environment.
    // 4) We can easily go from a constructor to a createFiber object literal if that
    // is faster.
    // 5) It should be easy to port this to a C struct and keep a C implementation
    // compatible.
    function createFiberImplClass(
    	tag: WorkTag,
    	pendingProps: mixed,
    	key: null | string,
    	mode: TypeOfMode,
    ): Fiber {
    	// $FlowFixMe[invalid-constructor]: the shapes are exact here but Flow doesn't like constructors
    	return new FiberNode(tag, pendingProps, key, mode); 
    }
    
    • 可以看到和 Fiber 树相关的是 235 - 240 行的内容,共有四个相关属性:child, sibling, return, index。这里 child 和 sibling 相信大伙都很熟悉了:
      • child:子节点
      • sibling:兄弟节点,特指在组件树中下一个兄弟节点
  • index 是用在对列表(比如用 for 循环然后对每个 element 都生成一个对应的组件时,这些组件就组成了一个列表)进行 diff 时,用来判断同一个节点在本次更新中是否发生了位移的,这个后面有机会看到 diff 算法时再做分析。这里着重看下 return。return 这里其实有一段开发者的 git message 注释,这里给大伙看看和翻译一下

Renamed fiber.parent -> fiber.return
This is not just the parent Instance but also the return Fiber for some piece of work. This clarifies and buys into this definition.
Basically, in the current model you will always pass into a fiber from the parent that you're going to return to. Even if you get aborted and reused this will be updated to the correct return fiber before you get back here.
// 以下是自己结合 GPT 渣翻
更名 fiber.parent 为 fiber.return
这不仅仅是父实例,它也代表了某个工作结束后要返回的目标 Fiber。 这个更名澄清了这个概念,并接受了这个定义。
基本上,在当前的模型中,你总是从要父 Fiber 中向当前 Fiber 传入一个要返回到的目标 Fiber。 即使更新过程被打断后重启,也会在回到这里之前更新为正确的返回目标 Fiber。

  • 也就是说从前这里是叫做 parent 的,但是为了更好的使变量能够正确地表达它们在流程中的作用,从 parent 改名为了 return。这一点在很多 Fiber 相关的流程中也可以看出来,例如 beginWork 中对于使用 memo 包裹并提供了自定义对比函数的组件的更新流程中:
    function updateMemoComponent(
      current: Fiber | null,
      workInProgress: Fiber,
      Component: any,
      nextProps: any,
      renderLanes: Lanes,
    ): null | Fiber {
      if (current === null) {
        // 省略了没有提供自定义对比函数,从 MemoComponent 转换成 SimpleMemoComponent 的一些处理
        // ......
        const child = createFiberFromTypeAndProps(
          Component.type,
          null,
          nextProps,
          workInProgress,
          workInProgress.mode,
          renderLanes,
        );
        child.ref = workInProgress.ref;
        child.return = workInProgress;
        workInProgress.child = child;
        return child;
      }
      const currentChild = ((current.child: any): Fiber); // This is always exactly one child
      // 省略了一些用于检查能不能跳过这次渲染流程的处理和开发环境相关的代码
      // ......
      const newChild = createWorkInProgress(currentChild, nextProps);
      newChild.ref = workInProgress.ref;
      newChild.return = workInProgress;
      workInProgress.child = newChild;
      return newChild;
    }
    
  • 到这里我们可以画出 Fiber 树当中的结构了: Drawing 2024-08-11 16.17.45.excalidraw.svg

参考资料