核心包

对于 react 而言的话核心的包就是

  • react

    • 主要是负责的是提供开发时候使用的核心包,关于很多的 api 的时候都是基于这个包来实现导出的

    • 一般使用的时候都是会结合渲染器和react-native 包进行结合使用和实现一些功能吧

  • react-dom

    • 是 react 与 web 平台进行连接的桥梁(可以在浏览器和nodejs 环境中进行使用),将 react-reconciler 中运行的结果输出到 web 界面上,在编写 react 应用的时候,大多数场景下,都是调用通过该 api 进行一个使用吧

    • 核心负责的功能就是进行对应的渲染器的功能吧

  • react-reconciler

    • react 得以运行的核心包(综合的协调 react-dom react scheduler)每个包之间的调用和配合使用,管理 react 应用状态的输出和输入,将输入的信号转换成输出信号传递给渲染器 react-dom

    • 接受输入(scheduleUpdateOnFiber), 将fiber树生成逻辑封装到一个回调函数中(涉及fiber树形结构, fiber.updateQueue队列, 调和算法等),

    • 把此回调函数(performSyncWorkOnRootperformConcurrentWorkOnRoot)送入scheduler进行调度

    • scheduler会控制回调函数执行的时机, 回调函数执行完成后得到全新的 fiber 树

    • 再调用渲染器(如react-dom, react-native等)将 fiber 树形结构最终反映到界面上

  • scheduler

    • 调度机制的核心实现, 控制由react-reconciler送入的回调函数的执行时机, 在concurrent模式下可以实现任务分片. 在编写react应用的代码时, 同样几乎不会直接用到此包提供的 api.

    • 核心任务就是执行回调(回调函数由react-reconciler提供)

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

架构分层

对于 react 而言的话核心是分为两层的,一个是 react 的 api 层,一个是 react 的内核层

  • 接口层(api层)

    • 接口层一般来自于 react 层吧,平时绝大多数的 api 的调度都是来自于这个包的

    • react 中进行改变渲染状态的方法由

      • class 组件中的话就是使用对应的:setState() 方法来实现对应的调度吧

      • function 组件中使用 hooks ,并且在内部发起对应的 dispatchAction 进行改变内部的 hook 对象

      • 改变 context 上下文的额时候也是间接的调度 setState 和 dispatchAction 来实现的讷

  • 内核层(core)

    • 调度器:scheduler 包,这个包的核心功能只有一个,就是进行执行回调用的讷(1)

      • 把 react-reconciler 提供的回调函数,包装为一个任务对象

      • 在内部维护对应的任务队列,优先级高的排在最前面(也就是优先级队列吧)

      • 循环的进行消费任务队列,直到队列为空

    • 构造器:react-reconciler 包,核心职责有三个吧(3)

      • 实现装载渲染器,渲染器必须是 HostConfig 协议:react-dom/react-native 保证需要的时候,可以正确的调用渲染的 api ,生成实际节点(DOM 节点)

      • 接受 react-dom 初始化的 createRoot/render 以及后续的 setState 的更新请求调度

      • 将 fiber 树的构造过程包装在一个回调函数中,并且将其回调函数传递给 scheduler 包,等待执行吧

    • 渲染器:react-dom 有两个核心责任

      • 引导 react 应用的启动(ReactDOM.render/ReactDOM.createRoot)

      • 实现 HostConfig 协议,能够将react-reconciler包构造出来的fiber树表现出来, 生成 dom 节点(浏览器中), 生成字符串(ssr).

React 两大工作循环

  • 分别是构建fiber包中的工作循环调度器中的工作循环

  • 分别是在 react-reconciler 和 scheduler 包中吧

  • 分别的表述是:任务调度循环(核心是循环的调度一些任务队列吧)+ fiber树构造循环

  • fiber 树的构造核心使用的深度优先遍历来实现吧

    • 深度优先遍历:实现方式有:1. 栈实现 2. 递归实现

区别和联系

  • 区别

    • 任务调度循环是使用的二叉堆为数据结构,循环执行堆的顶点,知道堆被清空来实现的吧

    • 任务调度循环的逻辑偏向宏观,他调度的是每一个任务(task)不是特别的关心任务具体是做什么的讷

      • 具体的任务其实就是执行回调函数 performSyncWorkRoot 或者说 performConcurrentWorkRoot 的吧

    • fiber 构造循环是以树为数据结构,从上至下进行执行深度优先遍历

    • fiber 构造循环的逻辑偏向具体的实现,它是任务的一部分(performSyncWorkOnRoot fiber 树的构造 DOM 渲染)

  • 联系

    • fiber 构造循环是任务调度循环中的任务一部分,他们是从属关系,每个任务都会重新构造一个 fiber 树吧

通过上文的描述, 两大循环的分工可以总结为: 大循环(任务调度循环)负责调度task, 小循环(fiber 构造循环)负责实现task .

react 运行的主干逻辑, 即将输入转换为输出的核心步骤, 实际上就是围绕这两大工作循环进行展开.

结合上文的宏观概览图(展示核心包之间的调用关系), 可以将 react 运行的主干逻辑进行概括:

  1. 输入: 将每一次更新(如: 新增, 删除, 修改节点之后)视为一次更新需求(目的是要更新DOM节点).

  2. 注册调度任务: react-reconciler收到更新需求之后, 并不会立即构造fiber树, 而是去调度中心scheduler注册一个新任务task, 即把更新需求转换成一个task.

  3. 执行调度任务(输出): 调度中心scheduler通过任务调度循环来执行task(task的执行过程又回到了react-reconciler包中).

    • fiber构造循环task的实现环节之一, 循环完成之后会构造出最新的 fiber 树.

    • commitRoottask的实现环节之二, 把最新的 fiber 树最终渲染到页面上, task完成.

主干逻辑就是输入到输出这一条链路, 为了更好的性能(如批量更新, 可中断渲染等功能), react在输入到输出的链路上做了很多优化策略, 比如本文讲述的任务调度循环fiber构造循环相互配合就可以实现可中断渲染.

React 高频对象

  • 事件对象(位于react-dom/events保障 react 应用能够响应 ui 交互)

  • ReactContext, ReactProvider, ReactConsumer对象

react 包

ReactElement

该对象的 type 主要是定义在我们的 shared 包中的讷,这样的设计的核心目的是实现类型的复用和实现吧

  • 在平时的开发中,所有的采用 jsx 语法书写的节点,都会被编译器转换的讷,都会以 React.craeteElement 的形式进行创建出来,形成 ReactElement 对象吧

type ReactElement {
  // 用户辨别 reactElement 对象的讷
  $$typeof: any,

  // react jsx 中的内部维护的属性吧
  type: any,  // 是此节点的类型
  key: any,  // 此节点包含的比如说 classname 这些元数据吧
  ref: any,  // react 获取得到元素实例的
  props: any,  // 组件需要的参数类型吧

  // react fiber 记录创建的 fiber 节点
  _owner: any,

  // __DEV__ dev 环境下的一些额外的信息总结吧
  _store: { validated: boolean },
  _self: React$Element<any>,
  _shadowChildren: any,
  _source: Source
}
  1. key属性在reconciler阶段会用到, 目前只需要知道所有的ReactElement对象都有 key 属性(且其默认值是 null, 这点十分重要, 在 diff 算法中会使用到).

  2. type属性决定了节点的种类:

  • 它的值可以是字符串(代表div,span等 dom 节点), 函数(代表function, class等节点), 或者 react 内部定义的节点类型(portal,context,fragment等)

  • reconciler阶段, 会根据 type 执行不同的逻辑(在 fiber 构建阶段详细解读).

    • 如 type 是一个字符串类型, 则直接使用.

    • 如 type 是一个ReactComponent类型, 则会调用其 render 方法获取子节点.

    • 如 type 是一个function类型,则会调用该方法获取子节点

定义了 20 种内部节点类型. 根据运行时环境不同, 分别采用 16 进制的字面量和Symbol进行表示.

ReactComponent 对象

  • 对于 ReactElement 来说,ReactComponent 仅仅就是 type 类型中的一种吧

  • ReactComponent 在开发中是十分常用的讷,使用也是十分高频的讷

class App extends React.Component {
  render() {
    return (
      <div className="app">
        <header>header</header>
        <Content />
        <footer>footer</footer>
      </div>
    );
  }
}

class Content extends React.Component {
  render() {
    return (
      <React.Fragment>
        <p>1</p>
        <p>2</p>
        <p>3</p>
      </React.Fragment>
    );
  }
}

export default App;

编译之后的代码(此处只编译了 jsx 语法, 并没有将 class 语法编译成 es5 中的 function), 可以更直观的看出调用逻辑.

createElement函数的第一个参数将作为创建ReactElementtype. 可以看到Content这个变量被编译器命名为App_Content, 并作为第一个参数(引用传递), 传入了createElement.

class App_App extends react_default.a.Component {
  render() {
    return /*#__PURE__*/ react_default.a.createElement(
      'div',
      {
        className: 'app',
      } /*#__PURE__*/,
      react_default.a.createElement('header', null, 'header') /*#__PURE__*/,

      // 此处直接将Content传入, 是一个指针传递
      react_default.a.createElement(App_Content, null) /*#__PURE__*/,
      react_default.a.createElement('footer', null, 'footer'),
    );
  }
}
class App_Content extends react_default.a.Component {
  render() {
    return /*#__PURE__*/ react_default.a.createElement(
      react_default.a.Fragment,
      null /*#__PURE__*/,
      react_default.a.createElement('p', null, '1'),
      /*#__PURE__*/

      react_default.a.createElement('p', null, '2'),
      /*#__PURE__*/

      react_default.a.createElement('p', null, '3'),
    );
  }
}

react-reconciler 包

react-reconciler包是react应用的中枢, 连接渲染器(react-dom)和调度中心(scheduler), 同时自身也负责 fiber 树的构造.

Fiber 对象

// 一个Fiber对象代表一个即将渲染或者已经渲染的组件(ReactElement), 一个组件可能对应两个fiber(current和WorkInProgress)
// 单个属性的解释在后文(在注释中无法添加超链接)
export type Fiber = {|
  tag: WorkTag,
  key: null | string,
  elementType: any,
  type: any,
  stateNode: any,
  return: Fiber | null,
  child: Fiber | null,
  sibling: Fiber | null,
  index: number,
  ref:
    | null
    | (((handle: mixed) => void) & { _stringRef: ?string, ... })
    | RefObject,
  pendingProps: any, // 从`ReactElement`对象传入的 props. 用于和`fiber.memoizedProps`比较可以得出属性是否变动
  memoizedProps: any, // 上一次生成子节点时用到的属性, 生成子节点之后保持在内存中
  updateQueue: mixed, // 存储state更新的队列, 当前节点的state改动之后, 都会创建一个update对象添加到这个队列中.
  memoizedState: any, // 用于输出的state, 最终渲染所使用的state
  dependencies: Dependencies | null, // 该fiber节点所依赖的(contexts, events)等
  mode: TypeOfMode, // 二进制位Bitfield,继承至父节点,影响本fiber节点及其子树中所有节点. 与react应用的运行模式有关(有ConcurrentMode, BlockingMode, NoMode等选项).

  // Effect 副作用相关
  flags: Flags, // 标志位
  subtreeFlags: Flags, //替代16.x版本中的 firstEffect, nextEffect. 当设置了 enableNewReconciler=true才会启用
  deletions: Array<Fiber> | null, // 存储将要被删除的子节点. 当设置了 enableNewReconciler=true才会启用

  nextEffect: Fiber | null, // 单向链表, 指向下一个有副作用的fiber节点
  firstEffect: Fiber | null, // 指向副作用链表中的第一个fiber节点
  lastEffect: Fiber | null, // 指向副作用链表中的最后一个fiber节点

  // 优先级相关
  lanes: Lanes, // 本fiber节点的优先级
  childLanes: Lanes, // 子节点的优先级
  alternate: Fiber | null, // 指向内存中的另一个fiber, 每个被更新过fiber节点在内存中都是成对出现(current和workInProgress)

  // 性能统计相关(开启enableProfilerTimer后才会统计)
  // react-dev-tool会根据这些时间统计来评估性能
  actualDuration?: number, // 本次更新过程, 本节点以及子树所消耗的总时间
  actualStartTime?: number, // 标记本fiber节点开始构建的时间
  selfBaseDuration?: number, // 用于最近一次生成本fiber节点所消耗的时间
  treeBaseDuration?: number, // 生成子树所消耗的时间的总和
|};

reactelement

fiber

Update 和 UpdateQueue 对象

fiber对象中有一个属性fiber.updateQueue, 是一个链式队列(即使用链表实现的队列存储结构)

export type Update<State> = {|
  eventTime: number,
  lane: Lane, // update所属的优先级

  tag: 0 | 1 | 2 | 3, //
  payload: any, // 载荷, 根据场景可以设置成一个回调函数或者对象
  callback: (() => mixed) | null, // 回调函数

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

// =============== UpdateQueue ==============
type SharedQueue<State> = {|
  pending: Update<State> | null,
|};

export type UpdateQueue<State> = {|
  baseState: State,
  firstBaseUpdate: Update<State> | null,
  lastBaseUpdate: Update<State> | null,
  shared: SharedQueue<State>,
  effects: Array<Update<State>> | null,
|};
  1. UpdateQueue

    • baseState: 表示此队列的基础 state

    • firstBaseUpdate: 指向基础队列的队首

    • lastBaseUpdate: 指向基础队列的队尾

    • shared: 共享队列

    • effects: 用于保存有callback回调函数的 update 对象, 在commit之后, 会依次调用这里的回调函数.

  2. SharedQueue

    • pending: 指向即将输入的update队列. 在class组件中调用setState()之后, 会将新的 update 对象添加到这个队列中来.

  3. Update

    • eventTime: 发起update事件的时间(17.0.2 中作为临时字段, 即将移出)

    • lane: update所属的优先级

    • tag: 表示update种类, 共 4 种. UpdateState,ReplaceState,ForceUpdate,CaptureUpdate

    • payload: 载荷, update对象真正需要更新的数据, 可以设置成一个回调函数或者对象.

    • callback: 回调函数. commit完成之后会调用.

    • next: 指向链表中的下一个, 由于UpdateQueue是一个环形链表, 最后一个update.next指向第一个update对象.

Hook 对象

Hook用于function组件中, 能够保持function组件的状态(与class组件中的state在性质上是相同的, 都是为了保持组件的状态).在react@16.8以后, 官方开始推荐使用Hook语法, 常用的 api 有useState,useEffect,useCallback等, 官方一共定义了14 种Hook类型.

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

type Update<S, A> = {|
  lane: Lane,
  action: A,
  eagerReducer: ((S, A) => S) | null,
  eagerState: S | null,
  next: Update<S, A>,
  priority?: ReactPriorityLevel,
|};

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

fiber对象中有一个属性fiber.memoizedState指向fiber节点的内存状态. 在function类型的组件中, fiber.memoizedState就指向Hook队列(Hook队列保存了function类型的组件状态).

scheduler 包

Task 对象

var newTask = {
  id: taskIdCounter++,
  callback,
  priorityLevel,
  startTime,
  expirationTime,
  sortIndex: -1,
};
  • id: 位移标识

  • callback: task 最核心的字段, 指向react-reconciler包所提供的回调函数.

  • priorityLevel: 优先级

  • startTime: 一个时间戳,代表 task 的开始时间(创建时间 + 延时时间).

  • expirationTime: 过期时间.

  • sortIndex: 控制 task 在队列中的次序, 值越小的越靠前.