前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >详细解读 Fiber 节点的每一个属性含义

详细解读 Fiber 节点的每一个属性含义

作者头像
用户6901603
发布2024-01-05 11:35:03
1470
发布2024-01-05 11:35:03
举报
文章被收录于专栏:不知非攻不知非攻

React 知命境第 36 篇,原创第 143 篇

在 React 中,每一个组件都会被转化为对应的 Fiber 节点。这也是我们常说的虚拟 DOM。这篇文章带大家一起来了解一下 Fiber 节点的字段到底都有些什么东西,他们分别代表什么含义。

Fiber 节点在 Reconciler 阶段被创建,它的构造函数如下。

代码语言:javascript
复制
function FiberNode(
  tag: WorkTag,
  pendingProps: mixed,
  key: null | string,
  mode: TypeOfMode,
) {
  // Instance
  this.tag = tag;
  this.key = key;
  this.elementType = null;
  this.type = null;
  this.stateNode = null;

  // Fiber
  this.return = null;
  this.child = null;
  this.sibling = null;
  this.index = 0;

  this.ref = null;

  this.pendingProps = pendingProps;
  this.memoizedProps = null;
  this.updateQueue = null;
  this.memoizedState = null;
  this.dependencies = null;

  // 
  this.mode = mode;

  // Effects
  this.flags = NoFlags;
  this.subtreeFlags = NoFlags;
  this.deletions = null;

  this.lanes = NoLanes;
  this.childLanes = NoLanes;

  this.alternate = null;
}

有了这个构造函数,我们就可以创建各种不同的 Fiber 节点

代码语言:javascript
复制
const createFiber = function(
  tag: WorkTag,
  pendingProps: mixed,
  key: null | string,
  mode: TypeOfMode,
): Fiber {
  return new FiberNode(tag, pendingProps, key, mode);
};
代码语言:javascript
复制
export function createHostRootFiber(
  tag: RootTag,
  isStrictMode: boolean,
  concurrentUpdatesByDefaultOverride: null | boolean,
): Fiber {
  ...
  return createFiber(HostRoot, null, null, mode);
}

1

如何项目中查看 Fiber 节点

在 chrome 中,将你的 React 项目运行起来,然后打开调试工具,在 Elements 面板中选中一个标签,右键,选择 Store as global variable

然后在 Console 面板中,就会发现有一个叫做 temp1 的全局变量被打印出来。我们执行如下指令查看详细对象信息

代码语言:javascript
复制
console.dir(temp1)

此时我们会发现一个叫做 _reactFiber$xxxxxxx 的字段,展开它,就是当前元素所对应的 Fiber 节点。如下图所示。此时我们能够看到 Fiber 节点的所有字段具体对应的值,有了这个东西之后,能够更加方便帮助我们学习 Fiber 节点的具体作用。

2

Instance

Fiber 节点的属性值比较多,因此源码中将其进行了分类。其中 Instance 表示构成该节点的基本信息,主要用于判断节点类型。

代码语言:javascript
复制
// Instance
this.tag = tag;
this.key = key;
this.elementType = null;
this.type = null;
this.stateNode = null;

tag 是 React 内部自定义的 WorkTag 类型,一共有 25 个值

代码语言:javascript
复制
export type WorkTag =
  | 0
  | 1
  | 2
  | 3
  | 4
  | 5
  | 6
  | 7
  | 8
  | 9
  | 10
  | 11
  | 12
  | 13
  | 14
  | 15
  | 16
  | 17
  | 18
  | 19
  | 20
  | 21
  | 22
  | 23
  | 24
  | 25;

他们所代表的具体含义如下

代码语言:javascript
复制
export const FunctionComponent = 0;
export const ClassComponent = 1;
// Before we know whether it is function or class
export const IndeterminateComponent = 2;
// Root of a host tree. Could be nested inside another node.
export const HostRoot = 3; 
// A subtree. Could be an entry point to a different renderer.
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;

我们只需要根据语义去理解每个 tag 代表什么类型的组件就可以了,跟使用语法结合起来理解非常简单。

key 为一个唯一值。用于在 diff 阶段能够快速跳过比较,常用于列表组件,普通组件可能没有该值。

type 表示节点类型。HostComponent 表示原生组件类型,此时 type 值为字符串。

代码语言:javascript
复制
type: 'div'

当组件为函数组件时,type 指向该函数声明本身

代码语言:javascript
复制
type: function App() {}

当组件为 context.Provider 时,type 指向如下

代码语言:javascript
复制
type: {
  $$typeof: Symbol(react.provider),
  _context: {
    $$typeof: Symbole(react.context),
    Consumer: {}
    Provider: {},
    displayName: 'Route',
    ...
  }
}

当节点为根节点时,type 值为 null.

当组件为 memo 包裹的节点时,type 值指向被包裹的组件

代码语言:javascript
复制
function _Child() {}

var Child = memo(_Child)
代码语言:javascript
复制
type: _Child,
elementType: {
  $$typeof: Symbol(react.meno),
  compare: null
}

elementType 大多数情况下都跟 type 的值保持一致,只有被 memo 包裹的节点不太一样,具体差别观察上面这个例子。

this.stateNode 指向节点的真实 DOM 对象

2

构建 Fiber tree

代码语言:javascript
复制
// Fiber
this.return = null;
this.child = null;
this.sibling = null;
this.index = 0;

整个页面所对应的 Fiber tree 由多个 Fiber 通过上面的几个引用构建而成。 this.return 指向父节点。this.child 指向子节点。this.sibling 指向下一个兄弟节点。

这里需要注意的是:每个元素的子节点,不是一个数组,而是一个 Fiber 对象。他们共同构成了一个完整的双向链表结构。当父节点有多个子节点时,p.child 只会指向第一个子节点。

例如这个例子,他对应的节点关系如下图

代码语言:javascript
复制
<div>
  <p></p>
  <p></p>
  <p></p>
</div>

this.index 表示当前节点是父元素的第几个子节点。

3

存储状态与 hook

代码语言:javascript
复制
  this.ref = null;
  this.pendingProps = pendingProps;
  this.memoizedProps = null;
  this.updateQueue = null;
  this.memoizedState = null;
  this.dependencies = null;

this.ref 表示当前组件的 ref,原生组件的 ref 指向真实 DOM,自定义组件的 ref 默认值为 null,可以通过 useImpretiveHandle 声明。

this.pendingProps 表示新传入的 props 对象

this.memoizedProps 表示上一次的 props 对象

this.memoizedState 是 hook 链表结构的起点。在 React 中,所有的 hook 被存储在一个链表中。每一个 hook 链表节点的格式如下

代码语言:javascript
复制
export type Hook = {
  memoizedState: any,
  baseState: any,
  baseQueue: Update<any, any> | null,
  queue: any,
  next: Hook | null,
};

不得不说,react 源码里的类型声明写得那真的是一个精彩,any 用得炉火纯青。

对于不同的 hook, hook.memoizedState 所存的值不一样。

例如,对于 useState 而言, hook.memoizedState 表示上一次的 state 值。hook.baseState 表示最新值。hook.next 指向下一个 hook,queue 又是一个新的链表结构,用来存储针对同一个 hook 的多次 setState 操作,它的节点结构如下

代码语言:javascript
复制
export type Update<S, A> = {
  lane: Lane, // 表示优先级
  action: A,
  hasEagerState: boolean,
  eagerState: S | null,
  next: Update<S, A>,
};

this.updateQueue 又是个链表结构,用于收集副作用的操作。该链表结构的节点类型如下

代码语言:javascript
复制
export type Effect = {
  tag: HookFlags,
  create: () => (() => void) | void,
  destroy: (() => void) | void,
  deps: Array<mixed> | null,
  next: Effect,
};

this.dependencies 的结构如下,该属性在更新时使用,用于判断是否依赖了 ContextProvider 中的值。

代码语言:javascript
复制
export type Dependencies = {
  lanes: Lanes,
  firstContext: ContextDependency<mixed> | null,
};

this.mode 的值类型为 TypeOfMode,具体类型如下

代码语言:javascript
复制
export type TypeOfMode = number;

export const NoMode = /*                         */ 0b000000;
// TODO: Remove ConcurrentMode by reading from the root tag instead
export const ConcurrentMode = /*                 */ 0b000001;
export const ProfileMode = /*                    */ 0b000010;
export const DebugTracingMode = /*               */ 0b000100;
export const StrictLegacyMode = /*               */ 0b001000;
export const StrictEffectsMode = /*              */ 0b010000;
export const ConcurrentUpdatesByDefaultMode = /* */ 0b100000;

4

更新类型

代码语言:javascript
复制
// Effects
this.flags = NoFlags;
this.subtreeFlags = NoFlags;
this.deletions = null;

this.flags 用来标记节点的更新类型,如果没有更新就是 NoFlags

常见的更新类型如下,更多的更新类型大家可以去源码中 ReactFiberFlags.js 中查看。

代码语言:javascript
复制
export type Flags = number;

// Don't change these two values. They're used by React Dev Tools.
export const NoFlags = /*                      */ 0b00000000000000000000000000;
export const PerformedWork = /*                */ 0b00000000000000000000000001;

// You can change the rest (and add more).
export const Placement = /*                    */ 0b00000000000000000000000010;
export const Update = /*                       */ 0b00000000000000000000000100;
export const Deletion = /*                     */ 0b00000000000000000000001000;
export const ChildDeletion = /*                */ 0b00000000000000000000010000;
export const ContentReset = /*                 */ 0b00000000000000000000100000;
export const Callback = /*                     */ 0b00000000000000000001000000;
export const DidCapture = /*                   */ 0b00000000000000000010000000;
export const ForceClientRender = /*            */ 0b00000000000000000100000000;
export const Ref = /*                          */ 0b00000000000000001000000000;
export const Snapshot = /*                     */ 0b00000000000000010000000000;
export const Passive = /*                      */ 0b00000000000000100000000000;
export const Hydrating = /*                    */ 0b00000000000001000000000000;
export const Visibility = /*                   */ 0b00000000000010000000000000;
export const StoreConsistency = /*             */ 0b00000000000100000000000000;
...

this.deletions 用于存储要被删除的 Fiber 节点。在 diff 过程中,如果发现节点存在但是不能复用,则会把节点存放在这里。

5

优先级

代码语言:javascript
复制
this.lanes = NoLanes;
this.childLanes = NoLanes;

该优先级由 expirationTime 来确定。

6

alternate

React 采用了双缓存策略来优化更新体验,因此大多数时候,会同时存在两棵 Fiber tree。大家经常听说的 diff 算法也是基于双缓存策略实现。

一棵树表示正在构建过程中的 Fiber Tree,命名为 workInProgress,另一棵树表示已经存在的树,命名为 current

当我们在创建新的 workInProgress 时,会通过 current.alternate 初始化。

代码语言:javascript
复制
export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {
  let workInProgress = current.alternate;
  ...

然后他们在代码中通过 alternate 相互链接。

代码语言:javascript
复制
workInProgress.alternate = current;
current.alternate = workInProgress;

除此之外,这两颗 Fiber Tree 的所有同级节点,也通过 alternate 相互链接,当在 diff 过程中发现可以复用时,可以直接跳过创建过程选择选择 fiber.alternate 以完成复用。

7

总结

在详细了解了 Fiber 对象中每一个字段之后,相信我们在接下来继续了解 React Reconciler 就会容易得多。协调器的整个过程都是围绕 Fiber 展开,这也有利于我们在使用 React 时变得更加合理。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2024-01-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 这波能反杀 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1
  • 2
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
相关产品与服务
对象存储
对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档