Fiber

React团队重写了React 的核心算法---reconciliation,一般将之前的算法叫stack reconciliation,现在的叫fiber reconciliation。

Stack reconciliation

算法的工作流程和函数的调用过程类似,react在进行组件渲染时,从setState开始到渲染完成整个过程是同步的,无法暂停,知道整个过程完成,如果组件比较大,js的执行会占用主线程过多的时间,会导致丢帧。打个比方, 假如我现在要更新1000个组件,每个组件平均花时间1ms,那么在1s内,浏览器的整个线程都被阻塞了,这时候用户在input上的任何操作都不会有反应,等到更新完毕,界面上突的一下就显示了原来用户的输入,这个体验是非常差的。

Fiber reconciliation

Fiber的特点

  • 暂停工作,并在之后可以返回再次开始;
  • 可以为不同类型的工作设置优先级;
  • 复用之前已经完成的工作;
  • 中止已经不再需要的工作。

React将任务分成小片,在一小片段的时间内运行这些分片任务,让主线程做优先级更高的事情,如果有任何待处理的事情,就回来完成工作,fiber即为一个分片任务

用一张经典的图来概括就是这样

结构

一个Fiber就是一个工作单元, React 的一个核心概念是 UI 是数据的投影 ,组件的本质可以看作输入数据,输出UI的描述信息(虚拟DOM树),即:

ui = f(data)

也就是说,渲染一个 React app,其实是在调用一个函数,函数本身会调用其它函数,形成调用栈,递归调用导致的调用栈我们本身无法控制, 只能一次执行完成。而 Fiber 就是为了解决这个痛点,可以去按需要打断调用栈,手动控制 stack frame——就这点来说,Fiber 可以理解为 virtual stack frame。

Fiber的结构

普通的js对象 ,它包含有关组件的输入和输出的信息。 下面是fiber一些重要的属性。

key type desc
tag Number FiberNode的类型,如HostRoot,HostComponent,HostText等有21种
key string ReactElement里面的key
elementType ReactElement.type 我们调用createElement的第一个参数
type function/class 异步组件resolved之后返回的内容,一般是function或者class
stateNode (FiberRoot,DomElement,ReactComponentInstance) FiberNode会通过stateNode绑定一些其他的对象,例如FiberNode对应的Dom、FiberRoot、ReactComponent实例
return FiberNode或null 表示父级 FiberNode
child FiberNode或null 表示第一个子 FiberNode
sibling FiberNode或null 表示紧紧相邻的下一个兄弟 FiberNode
alternate FiberNode或null Fiber调度算法采取了双缓冲池算法,current的alternate指向workInProgerss ,而workInProgress的alternate指向current,在创建workInProgerss的时候会尽量重用current.alternate
pendingProps Object 表示新的props,来自element的props
memoizedProps Object 表示上一次render时的props
memoizedState Object 表示处理后的新state
updateQueue UpdateQueue 更新队列,队列内放着即将要发生的变更状态
effectTag Number 16进制的数字,可以理解为通过一个字段标识n个动作,如Placement、Update、Deletion、Callback……所以源码中看到很多 &=
firstEffect FiberNode,null 与副作用操作遍历流程相关 当前节点下,第一个需要处理的side effect
nextEffect FiberNode,null 表示下一个将要处理的side effect
lastEffect FiberNode,null 表示最后一个将要处理的side effect
pendingWorkPriority number 工作的优先级,数字越大,优先级越大
expirationTime number 代表任务在未来的哪个时间点应该被完成
childExpirationTime number 快速确定子树中是否有不在等待的变化

EffectTag

当state和props变化时,会引起视图的重新渲染,这个过程叫做side effect,而side effect分为很多种情况,具体要执行哪种effect,在react中是通过effectTag属性记录的,在源码中以二进制的形式保存,因此可以记录多种操作。

export type SideEffectTag = number;

// Don't change these two values. They're used by React Dev Tools.
export const NoEffect = /*              */ 0b000000000000;
export const PerformedWork = /*         */ 0b000000000001;

// You can change the rest (and add more).
export const Placement = /*             */ 0b000000000010;
export const Update = /*                */ 0b000000000100;
export const PlacementAndUpdate = /*    */ 0b000000000110;
export const Deletion = /*              */ 0b000000001000;
export const ContentReset = /*          */ 0b000000010000;
export const Callback = /*              */ 0b000000100000;
export const DidCapture = /*            */ 0b000001000000;
export const Ref = /*                   */ 0b000010000000;
export const Snapshot = /*              */ 0b000100000000;
export const Passive = /*               */ 0b001000000000;

// Passive & Update & Callback & Ref & Snapshot
export const LifecycleEffectMask = /*   */ 0b001110100100;

// Union of all host effects
export const HostEffectMask = /*        */ 0b001111111111;

export const Incomplete = /*            */ 0b010000000000;
export const ShouldCapture = /*         */ 0b100000000000;
  • NoEffect:一般作为effectTag的初始值,或者用于effectTag的比较判断,表示NoWork
  • PerformedWork:由react devtools读取,NoEffect和PerformedWork都不会被committed,当创建effcet list(后面会介绍)时,会跳过NoEffect和PerformedWork
  • Placement:向树中插入新的子节点,对应的状态为MOUNTING,当执行commitPlacement函数完成插入后,清除该标志位
  • Update:当props、state、context发生变化,或者forceUpdate时,会标记为Update,检查到标记后,执行commitUpdate函数进行属性更新,与其相关的生命周期函数为componentDidMount和componentDidUpdate
  • Deletion:标记将要卸载的节点,检查到标记后,执行commitDeletion函数对组件进行卸载,在节点树中删除对应对节点,与其相关的生命周期函数为componentWillUnmount
  • ContentReset 当从文本域节点切换到非文本域或空节点时,打上此标记,将文本内容进行重置,文本域节点包括textarea、option、noscript、string、number和直接在标签中写入的__html。当检测到标记后,执行commitResetTextContent函数将对应节点的text清空
  • Callback:当setState、forceUpdate有callback函数,或者在Commit阶段捕获到错误时,会更新update.callback,并标记Callback,随后检测到标记后会触发commitLifeCycles函数,根据不同到组件类型进行不同的commit
  • DidCapture:针对于懒加载的React.Suspense(SuspenseComponent)组件提供的标志位,DidCapture位置位表示要渲染的组件被挂起,进而先渲染fallback的内容
  • ShouldCapture:标记是否需要将节点挂起,一般捕获边界错误或者超时会置位,随后用于判断是否进行DidCapture
  • Ref:当节点中存在属性ref时,会进行markRef当标记,随后会在commitAllLifeCycles阶段执行commitAttachRef触发相应当ref回调函数
  • Snapshot:在渲染更新之前,当前后当props或state发生变化时,触发getSnapshotBeforeUpdate生命周期钩子

Hooks出来之后新增了HookEffectTag,用于标记ReactHook的effect的tag,后面再作介绍


export const NoEffect = /*             */ 0b00000000; 0
export const UnmountSnapshot = /*      */ 0b00000010; 2
export const UnmountMutation = /*      */ 0b00000100; 4
export const MountMutation = /*        */ 0b00001000; 8
export const UnmountLayout = /*        */ 0b00010000; 16
export const MountLayout = /*          */ 0b00100000; 32
export const MountPassive = /*         */ 0b01000000; 64
export const UnmountPassive = /*       */ 0b10000000; 128

FiberRoot

type BaseFiberRootProperties = {|
  // The type of root (legacy, batched, concurrent, etc.)
  tag: RootTag,

  //root节点,render方法接收的第二个参数
  containerInfo: any,
  // 只有在持久更新中会用到,也就是不支持增量更新的平台,react-dom不会用到
  pendingChildren: any,
  // 当前应用对应的Fiber对象,是Root Fiber
  current: Fiber,

  pingCache:
    | WeakMap<Thenable, Set<ExpirationTime>>
    | Map<Thenable, Set<ExpirationTime>>
    | null,

  finishedExpirationTime: ExpirationTime,
  // 已经完成的并准备进行commit的 work-in-progress HostRoot .
  finishedWork: Fiber | null,
  // 通过setTimeout设置的返回内容,如果被新的任务代替,用来取消pending状态的timeout
  timeoutHandle: TimeoutHandle | NoTimeout,
  // 顶层context对象,只有主动调用`renderSubtreeIntoContainer`时才会有用
  context: Object | null,
  pendingContext: Object | null,
  // 确定是否应该在初始挂载时进行hydrate
  +hydrate: boolean,
  // 批量更新列表,此列表指示是否应延迟提交,还包含完成回调。
  firstBatch: Batch | null,
  // Scheduler.scheduleCallback返回的节点
  callbackNode: *,
  // 和root相关的回调到期时间
  callbackExpirationTime: ExpirationTime,
  // 树中最早的挂起到期时间
  firstPendingTime: ExpirationTime,
  // 树中最新的挂起到期时间
  lastPendingTime: ExpirationTime,
  // The time at which a suspended component pinged the root to render again
  pingTime: ExpirationTime,
|};

ReactWorkTag

export const FunctionComponent = 0;
export const ClassComponent = 1;
export const IndeterminateComponent = 2; // Before we know whether it is function or class
export const HostRoot = 3; // Root of a host tree. Could be nested inside another node.
export const HostPortal = 4; // A subtree. Could be an entry point to a different renderer.
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 DehydratedSuspenseComponent = 18;
export const EventComponent = 19;
export const EventTarget = 20;
export const SuspenseListComponent = 21;

Update & UpdateQueue

export type Update<State> = {
    //更新的过期时间
  expirationTime: ExpirationTime,
  suspenseConfig: null | SuspenseConfig,
    // export const UpdateState = 0;
    // export const ReplaceState = 1;
    // export const ForceUpdate = 2;
    // export const CaptureUpdate = 3;
  tag: 0 | 1 | 2 | 3,
    //更新内容,比如`setState`接收的第一个参数
  payload: any,
    // 对应的回调,`setState`,`render`都有
  callback: (() => mixed) | null,
    // 指向下一个更新
  next: Update<State> | null,
  // 指向下一个`side effect`
  nextEffect: Update<State> | null,
};

export type UpdateQueue<State> = {
    // 每次操作完更新之后的`state`
  baseState: State,
    // 队列中的第一个`Update`
  firstUpdate: Update<State> | null,
    // 队列中的最后一个`Update`
  lastUpdate: Update<State> | null,
    // 第一个捕获类型的`Update`
  firstCapturedUpdate: Update<State> | null,
    // 最后一个捕获类型的`Update`
  lastCapturedUpdate: Update<State> | null,
    // 第一个`side effect`
  firstEffect: Update<State> | null,
    // 最后一个`side effect`
  lastEffect: Update<State> | null,
    // 第一个和最后一个捕获产生的`side effect`
  firstCapturedEffect: Update<State> | null,
  lastCapturedEffect: Update<State> | null,
};

在React中有很多单链表的数据结构,包括上面提到的effect和UpdateQueue。

updateQueue是更新队列,他是一个单向链表,用于更新state,并重绘组件,firstUpdate和lastUpdate分别指向链表的头部和尾部,存储这update对象。以HostRoot为例,从beginWork开始,直到遍历完整个UpdateQueue链表,获取新的状态,当新旧状态不一样时,将effectTag的Update置位,进入更新阶段。

workInProgress 双缓冲池技术

workInProgress tree是reconcile过程中从fiber tree建立的当前进度快照,所有的工作都是在这颗树上进行,用于计算更新,完成reconciliation过程。

workInProgress.alternate = current;
current.alternate = workInProgress;

他和当前的fiber通过alternate进行关联,在构建workInProgress 时,会取current.alternate,存在则复用,不存在则创建。这样做能够复用内部对象(fiber),节省内存分配、GC的时间开销。

workInProgress tree构造完毕,得到的就是新的fiber tree,当进入commit阶段就把current指向了workInProgress

root.current = finishedWork;

tag

用于标记组件类型,具体分类取值如下:

var FunctionComponent = 0;
var ClassComponent = 1;
var IndeterminateComponent = 2; // Before we know whether it is function or class
var HostRoot = 3; // Root of a host tree. Could be nested inside another node.
var HostPortal = 4; // A subtree. Could be an entry point to a different renderer.
var HostComponent = 5;
var HostText = 6;
var Fragment = 7;
var Mode = 8;
var ContextConsumer = 9;
var ContextProvider = 10;
var ForwardRef = 11;
var Profiler = 12;
var SuspenseComponent = 13;
var MemoComponent = 14;
var SimpleMemoComponent = 15;
var LazyComponent = 16;
var IncompleteClassComponent = 17;
var DehydratedSuspenseComponent = 18;

mode

mode在创建时进行设置,在创建之后,mode在Fiber的整个生命周期内保持不变,可能的取值:

var NoContext = 0; // 同步模式
var ConcurrentMode = 1; // 异步模式
var StrictMode = 2; //严格模式,一般用于开发中,
var ProfileMode = 4; // 分析模式,一般用于开发中

results matching ""

    No results matching ""