Dive in React

42 阅读16分钟

React宏观设计理念

核心包及架构分层

各司其职。

  1. 基础包(士兵):react

react 基础包, 只提供定义 react 组件(ReactElement)的必要函数, 一般来说需要和渲染器(react-dom,react-native)一同使用. 在编写react应用的代码时, 大部分都是调用此包的 api.

  1. 渲染器:react-dom/react-native-renderer/react-test-renderer

是 react 与 web 平台或native平台连接的桥梁(可以在浏览器和 nodejs 环境中使用), 将react-reconciler中的运行结果输出到 web 界面上. 在编写react应用的代码时,大多数场景下, 能用到此包的就是一个入口函数ReactDOM.render(<App/>, document.getElementById('root')), 其余使用的 api, 基本是react包提供的.

React-dom: react components -> DOM

React-native-renderer: react components -> native views

React-test-renderer: react components -> JSON tree, for test using

  1. 协调器(外交官):react-reconciler

react 得以运行的核心包,核心是处理React的diff算法,通过对比fiber tree前后的差异来决定DOM需要做哪些更新。react各大包中的交际花(综合协调react-dom,react,scheduler各包之间的调用与配合). 管理 react 应用状态的输入和结果的输出. 将输入信号最终转换成输出信号传递给渲染器.

  • 输入:(scheduleUpdateOnFiber), 将fiber树生成逻辑封装到一个回调函数中(涉及fiber树形结构, fiber.updateQueue队列, 调和算法等),
  • 把此回调函数(performSyncWorkOnRootperformConcurrentWorkOnRoot)送入scheduler进行调度
  • scheduler会控制回调函数执行的时机, 回调函数执行完成后得到全新的 fiber 树
  • 再调用渲染器(如react-dom, react-native等)将 fiber 树形结构最终反映到界面上

相较于低版本的stack reconcile的优势:

  • 能够将可中断的工作分成块。
  • 能够对工作进行优先级排序、重置和重复利用。
  • 能够在 React 中在父组件和子组件之间来回传递,以支持布局。
  • 能够从 render() 中返回多个元素。
  • 更好地支持错误边界。

算法核心假设:

  • 不同类型的两个元素将产生不同的树。
  • 开发人员可以通过 key 属性暗示哪些子元素可能在不同的渲染中保持稳定。
  1. 调度器(将军):scheduler

任务调度中枢,核心任务是管理任务优先级,将由react-reconciler送入的回调函数,维护一个任务队列,根据优先级等决定其执行时机,并执行。在concurrent模式下可以实现任务分片.

  • 核心任务就是执行回调(回调函数由react-reconciler提供)
  • 通过控制回调函数的执行时机, 来达到任务分片的目的, 实现可中断渲染(concurrent模式下才有此特性)

架构模块关系图

备注:玫红色输入,绿色输出

两大工作循环workLoop

  1. fiber构造循环

数据结构为树。从上至下执行深度优先遍历父-子-兄弟,直到回溯到根节点。

源码位于ReactFiberWorkLoop.js, 控制 fiber 树的构造, 整个过程是一个深度优先遍历.

  1. 任务调度循环

数据结构为二叉树小堆树。循环执行的顶点, 直到被清空

源码位于Scheduler.js, 它是react应用得以运行的保证, 它需要循环调用, 控制所有任务(task)的调度.

这两个循环对应的 js 源码不同于其他闭包(运行时就是闭包), 其中定义的全局变量, 不仅是该作用域的私有变量, 更用于控制react应用的执行过程.

三大优先级管理

reconciler从输入到输出一共经历了 4 个阶段, 在每个阶段中都会涉及到与优先级相关的处理. 正是通过优先级的灵活运用, React实现了可中断渲染,时间切片(time slicing),异步渲染(suspense)等特性.

  1. LanePriority: fiber优先级

位于react-reconciler包, 也就是Lane(车道模型).

export const TotalLanes = 31;

export const NoLanes: Lanes = /*                        */ 0b0000000000000000000000000000000;
export const NoLane: Lane = /*                          */ 0b0000000000000000000000000000000;

export const SyncHydrationLane: Lane = /*               */ 0b0000000000000000000000000000001;
export const SyncLane: Lane = /*                        */ 0b0000000000000000000000000000010;
export const SyncLaneIndex: number = 1;

export const InputContinuousHydrationLane: Lane = /*    */ 0b0000000000000000000000000000100;
export const InputContinuousLane: Lane = /*             */ 0b0000000000000000000000000001000;

export const DefaultHydrationLane: Lane = /*            */ 0b0000000000000000000000000010000;
export const DefaultLane: Lane = /*                     */ 0b0000000000000000000000000100000;

export const SyncUpdateLanes: Lane = enableUnifiedSyncLane
  ? SyncLane | InputContinuousLane | DefaultLane
  : SyncLane;

const TransitionHydrationLane: Lane = /*                */ 0b0000000000000000000000001000000;
const TransitionLanes: Lanes = /*                       */ 0b0000000001111111111111110000000;
const TransitionLane1: Lane = /*                        */ 0b0000000000000000000000010000000;
const TransitionLane2: Lane = /*                        */ 0b0000000000000000000000100000000;
const TransitionLane3: Lane = /*                        */ 0b0000000000000000000001000000000;
const TransitionLane4: Lane = /*                        */ 0b0000000000000000000010000000000;
const TransitionLane5: Lane = /*                        */ 0b0000000000000000000100000000000;
const TransitionLane6: Lane = /*                        */ 0b0000000000000000001000000000000;
const TransitionLane7: Lane = /*                        */ 0b0000000000000000010000000000000;
const TransitionLane8: Lane = /*                        */ 0b0000000000000000100000000000000;
const TransitionLane9: Lane = /*                        */ 0b0000000000000001000000000000000;
const TransitionLane10: Lane = /*                       */ 0b0000000000000010000000000000000;
const TransitionLane11: Lane = /*                       */ 0b0000000000000100000000000000000;
const TransitionLane12: Lane = /*                       */ 0b0000000000001000000000000000000;
const TransitionLane13: Lane = /*                       */ 0b0000000000010000000000000000000;
const TransitionLane14: Lane = /*                       */ 0b0000000000100000000000000000000;
const TransitionLane15: Lane = /*                       */ 0b0000000001000000000000000000000;

const RetryLanes: Lanes = /*                            */ 0b0000011110000000000000000000000;
const RetryLane1: Lane = /*                             */ 0b0000000010000000000000000000000;
const RetryLane2: Lane = /*                             */ 0b0000000100000000000000000000000;
const RetryLane3: Lane = /*                             */ 0b0000001000000000000000000000000;
const RetryLane4: Lane = /*                             */ 0b0000010000000000000000000000000;

export const SomeRetryLane: Lane = RetryLane1;

export const SelectiveHydrationLane: Lane = /*          */ 0b0000100000000000000000000000000;

const NonIdleLanes: Lanes = /*                          */ 0b0000111111111111111111111111111;

export const IdleHydrationLane: Lane = /*               */ 0b0001000000000000000000000000000;
export const IdleLane: Lane = /*                        */ 0b0010000000000000000000000000000;

export const OffscreenLane: Lane = /*                   */ 0b0100000000000000000000000000000;
export const DeferredLane: Lane = /*                    */ 0b1000000000000000000000000000000;

2. ### SchedulerPriority:调度优先级

位于scheduler包.

export const unstable_ImmediatePriority = 1;
export const unstable_UserBlockingPriority = 2;
export const unstable_NormalPriority = 3;
export const unstable_IdlePriority = 5;
export const unstable_LowPriority = 4;

3. ### ReactPriorityLevel : 优先级等级

位于react-reconciler包中的SchedulerWithReactIntegration.js, 负责上述 2 套优先级体系的转换.

type ReactPriorityLevelsType = {
  ImmediatePriority: number,
  UserBlockingPriority: number,
  NormalPriority: number,
  LowPriority: number,
  IdlePriority: number,
  NoPriority: number,
};

React中的重点对象

  1. ReactElement对象

export type ReactElement = {
  $$typeof: any,
  type: any,
  key: any,
  ref: any,
  props: any,
  // __DEV__ or for string refs
  _owner: any,

  // __DEV__
  _store: {validated: boolean, ...},
  _debugInfo: null | ReactDebugInfo,
  _debugStack: Error,
  _debugTask: null | ConsoleTask,
};

2. ## Fiber对象

一个Fiber是一个工作单元unit of work,的目的是充分利用scheduling。尤其是当需要:

  • 暂停工作,过一会儿再回来。
  • 为不同类型的工作分配优先级。
  • 重复利用以前完成的工作。
  • 如果不再需要,中止工作。

双缓冲技术: 一个Fiber对象代表一个即将渲染或者已经渲染的组件(ReactElement), 一个组件可能对应两个fiber(current和WorkInProgress)

export type Fiber = {
  // 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.
  // 指向内存中的该fiber的副本,双缓存策略current和workInProgress
  alternate: Fiber | null,
  
  
  // Tag identifying the type of fiber.
  tag: WorkTag,

  // Unique identifier of this child.
  key: null | string,

  // The value of element.type which is used to preserve the identity during
  // reconciliation of this child.
  elementType: any,

  // The resolved function/class/ associated with this fiber.
  type: any,

  // The local state associated with this fiber.
  // DFS: 探寻阶段存储当前fiber节点的具体状态,回溯阶段指向对应的DOM实例
  stateNode: any,

  // 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,

  refCleanup: null | (() => void),

  // Input is the data coming into process this fiber. Arguments. Props.
  pendingProps: any, // This type will be more specific once we overload the tag. 从`ReactElement`对象传入的 props. 用于和`fiber.memoizedProps`比较可以得出属性是否变动
  memoizedProps: any, // The props used to create the output. 上一次生成子节点时用到的属性, 生成子节点之后保持在内存中

  // A queue of state updates and callbacks.
  // 存储state更新的队列, 每次发起更新都会创建一个update对象,添加到这个队列中.
  updateQueue: mixed,
  

  // The state used to create the output
  memoizedState: any, // 上一次生成子节点之后保持在内存中的局部状态。函数式组件中,指向Hook队列。

  // Dependencies (contexts, events) for this fiber, if it has any
  dependencies: Dependencies | null,

  // 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, // 7种:ConcurrentMode,StrictLegacyMode等。7位二进制表示。

  // Effect
  flags: Flags, //  副作用标记:Update, Placement,Snapshot,Callback等. 20位二进制表示。
  subtreeFlags: Flags,
  deletions: Array<Fiber> | null,

  // Lanes通道优先级: 31位二进制表示。SyncLane,IdleLane,NoLane等
  lanes: Lanes, //
  childLanes: Lanes,

  // 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 descendants 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

  _debugInfo?: ReactDebugInfo | null,
  _debugOwner?: ReactComponentInfo | Fiber | null,
  _debugIsCurrentlyTiming?: boolean,
  _debugNeedsRemount?: boolean,

  // Used to verify that the order of hooks does not change between renders.
  _debugHookTypes?: Array<HookType> | null,
};

3. ## UpdateQueue及Hook对象

react-reconciler包中。状态更新环形链表,组件状态更新后会更新。

2.1 类函数组件的更新:

// 1. class:ReactFiberClassUpdateQueue
// 环形链表
export type Update<State> = {
  lane: Lane,

  tag: UpdateState | ReplaceState | ForceUpdate | CaptureUpdate,
  payload: any, // update对象真正需要更新的数据,可是回调函数或对象
  callback: (() => mixed) | null, // 回调函数. commit完成之后会调用

  next: Update<State> | null, // 指向链表中的下一个,UpdateQueue是一个环形链表, 最后一个update.next指向第一个update对象
};

export type SharedQueue<State> = {
  pending: Update<State> | null, // 指向即将输入的update队列. 在class组件中调用setState()之后, 会将新的 update 对象添加到这个队列中来
  lanes: Lanes,
  hiddenCallbacks: Array<() => mixed> | null,
};

export type UpdateQueue<State> = {
  baseState: State,
  firstBaseUpdate: Update<State> | null,
  lastBaseUpdate: Update<State> | null,
  shared: SharedQueue<State>,
  callbacks: Array<() => mixed> | null, //  用于保存有callback回调函数的 update 对象, 在commit之后, 会依次调用这里的回调函数.
};

// 2. hooks

2.2 函数式组件的更新:

export type Hook = {
  memoizedState: any,
  baseState: any,
  baseQueue: Update<any, any> | null,
  queue: any,
  next: Hook | null,
};

export type Update<S, A> = {
  lane: Lane,
  revertLane: Lane,
  action: A,
  hasEagerState: boolean,
  eagerState: S | null,
  next: Update<S, A>, // 指向该function组件的下一个Hook对象, 使得多个Hook之间也构成了一个链表.
};

export type UpdateQueue<S, A> = {
  pending: Update<S, A> | null,
  lanes: Lanes,
  dispatch: (A => mixed) | null,
  lastRenderedReducer: ((S, A) => S) | null,
  lastRenderedState: S | null,
};

  1. Task对象

schedule包中。调度任务的个体。

export opaque type Task = {
  id: number,
  callback: Callback | null,
  priorityLevel: PriorityLevel,
  startTime: number,
  expirationTime: number,
  sortIndex: number, // 制 task 在队列中的次序, 值越小的越靠前。
  isQueued?: boolean,
};

5. ## 三大对象的关系:ReactElement, Fiber, DOM

  1. 双缓冲技术Double buffering: fiberRoot.currentworkInProgress

  1. 构造过程中: fiberRoot.current指向当前界面对应的fiber树.

  1. 新的fiber树workInProgress构造完成并渲染, 切换fiberRoot.current指针, 使其继续指向当前界面对应的fiber树(原来代表界面的 fiber 树, 变成了内存中).

reconciler -协调器 (TODO)

Reconciler负责比较组件树的差异(即diffing过程),并计算出实际DOM需要进行的最小更新。在Fiber架构中,Reconciler可以在必要时暂停、中断和恢复工作。

4个阶段:

  1. 输入阶段: 衔接react-dom包, 承接fiber更新请求(参考React 应用的启动过程).
  2. 注册调度任务: 与调度中心(scheduler包)交互, 注册调度任务task, 等待任务回调(参考React 调度原理(scheduler)).
  3. 执行任务回调构建阶段: 在内存中构造出fiber树DOM对象(参考fiber 树构造(初次创建)和 fiber 树构造(对比更新)).异步执行,可中断和恢复。
  4. commit输出阶段: 与渲染器(react-dom)交互, 渲染DOM节点. 同步执行,不可中断:

github.com/7kms/react-…

  1. 输入

  1. 注册调度任务

  1. 执行任务回调

Fiber树构造(TODO)

关键方法:workLoopSync -> performUnitOfWork -> beginWork

   ->completeUnitOfWork->completeWork

暂时无法在Lark文档外展示此内容

关键文件:

eg:

class App extends React.Component {
  componentDidMount() {
    console.log(`App Mount`);
    console.log(`App 组件对应的fiber节点: `, this._reactInternals);
  }
  render() {
    return (
      <div className="app">
        <header>header</header>
        <Content />
      </div>
    );
  }
}

class Content extends React.Component {
  componentDidMount() {
    console.log(`Content Mount`);
    console.log(`Content 组件对应的fiber节点: `, this._reactInternals);
  }
  render() {
    return (
      <React.Fragment>
        <p>1</p>
        <p>2</p>
      </React.Fragment>
    );
  }
}
export default App;
  1. 初次创建Fiber树

深度优先遍历:父-子-兄弟

深度优先遍历中,每个节点会经历两个阶段:

  1. 探寻阶段 beginWork: 创建fiber节点

    1. 根据 ReactElement对象创建所有的fiber节点, 最终构造出fiber树形结构(设置returnsibling指针)
    2. 设置fiber.flags: 二进制形式变量, 用来标记 fiber节点 的增,删,改状态, 等待completeWork阶段处理
    3. 设置fiber.stateNode局部状态: 如Class类型节点: fiber.stateNode=new Class()
    4. 设置fiber.memorizedState<-fiber.pendingProps, fiber.updateQueue
  2. 回溯阶段 completeWork:处理上面beginWork 阶段已经创建出来的 fiber 节点(completeUnitOfWork(unitOfWork))

    1. 生成DOM:创建当前fiber节点的DOM实例;设置fiber.StateNode属性,指向DOM实例对象;设置DOM对象的属性, 绑定事件等
    2. 更新副作用队列:把当前 fiber 对象的副作用队列(firstEffectlastEffect)添加到父节点的副作用队列之后, 更新父节点的firstEffectlastEffect指针.
    3. 识别处理fiber.flags:判断当前 fiber 是否有副作用(增,删,改), 如果有, 需要将当前 fiber 加入到父节点的effects队列, 等待commit阶段处理.
  3. 更新Fiber树

import React from 'react';

class App extends React.Component {
  state = {
    list: ['A', 'B', 'C'],
  };
  onChange = () => {
    this.setState({ list: ['C', 'A', 'X'] });
  };
  componentDidMount() {
    console.log(`App Mount`);
  }
  render() {
    return (
      <>
        <Header />
        <button onClick={this.onChange}>change</button>
        <div className="content">
          {this.state.list.map((item) => (
            <p key={item}>{item}</p>
          ))}
        </div>
      </>
    );
  }
}

class Header extends React.PureComponent {
  render() {
    return (
      <>
        <h1>title</h1>
        <h2>title2</h2>
      </>
    );
  }
}
export default App;

  1. 输出 - 渲染

关键方法:commitRoot -> commitRootImpl

commitRoot中同时使用到了渲染优先级调度优先级

4.1 渲染前:

4.2 渲染

任务:

  1. 处理副作用队列: 副作用队列在根节点, 即HostRootFiber节点。根据fiber.flags分别处理
  2. 调用渲染器,将最新的 DOM 节点渲染到界面上。已经在内存中, 只是还没渲染,放在首个HostComponent类型的fiber节点的stateNode.

scheduler - 调度器(TODO)

主要职责:

关键函数:

关键文件:

两个任务队列:taskQueue, timerQueue.

React 采用“拉”的方法,只有在必要时才会延迟计算。React 不是一般的数据处理库。它是用来构建用户界面的库。它在应用程序中独具优势,能够知道哪些计算是当前相关的,哪些是不相关的。

taskQueue对象

数据结构是小顶堆数组,通过sortIndex排序,始终保证数组中的第一个task对象优先级最高。

sortIndex:

  1. startTime > currentTime还未到期的任务:sortIndex=startTime
  2. 否则:sortIndex = expirationTime
export opaque type Task = {
  id: number,
  callback: Callback | null,
  priorityLevel: PriorityLevel,
  startTime: number,
  expirationTime: number,
  sortIndex: number, // 制 task 在队列中的次序, 值越小的越靠前。
  isQueued?: boolean,
};

四、其它

github.com/acdlite/rea…

blog.ag-grid.com/inside-fibe…