Skip to content
大纲

React 中涉及的基本概念

JSX

采用 JSX 语法书写的节点,都会被编译器转换,最终会以 React.createElement(...) 的方式,创建出来一个与之对应的 ReactElement 对象。

ReactElement 结构

js
export type ReactElement = {|
  // 可以是:字符串 dom 节点--直接使用;函数:function、class 类型--调用其 render 方法获取子节点 或 调用该方法获取子节点;内部自定义节点类型
  $$typeof: any,

  // 内部属性
  type: any, // 表明其种类
  key: any,
  ref: any,
  props: any,

  // ReactFiber 记录创建本对象的 Fiber 节点,还未与 Fiber 树关联之前,该属性为 null
  _owner: any,

  // __DEV__ dev 环境下的一些额外信息,如文件路径,文件名,行列信息等
  _store: { validated: boolean, ... },
  _self: React$Element<any>,
  _shadowChildren: any,
  _source: Source
|}
  • ReactComponent 是 class 类型,继承父类 Component
    • 拥有特殊的方法 (setState,forceUpdate) 和特殊的属性 (context,updater 等).
    • 在 reconciler 阶段,会依据 ReactElement 对象的特征,生成对应的 fiber 节点。当识别到 ReactElement 对象是 class 类型的时候,会触发 ReactComponent 对象的生命周期,并调用其 render 方法,生成 ReactElement 子节点。
      • 在 render 之后 (reconciler 阶段) 才生成的,父级对象和子级对象之间是通过 props.children 属性进行关联的
      • ReactElement 树 (暂且用树来表述) 和 fiber 树是以 props.children 为单位先后交替生成的,当 ReactElement 树构造完毕,fiber 树也随后构造完毕。
      • reconciler 阶段会根据 ReactElement 的类型生成对应的 fiber 节点 (不是一一对应,比如 Fragment 类型的组件在生成 fiber 节点的时候会略过).
  • function 类型
    • 如果在 function 类型的组件中没有使用 Hook,在 reconciler 阶段所有有关 Hook 的处理都会略过,最后调用该 function 拿到子节点 ReactElement.
    • 如果使用了 Hook

Fiber 结构

js
export type Fiber = {|
  tag: WorkTag, // fiber 的类型,根据 ReactElement 组件的 type 进行生成
  key: null | string, // 和 ReactElement 组件的 key 一致。
  elementType: any, //一般来讲和 ReactElement 组件的 type 一致 比如 div ul
  type: any, // 一般来讲和 fiber.elementType 一致。一些特殊情形下,比如在开发环境下为了兼容热更新
  stateNode: any, // 真实 DOM 是谁
  return: Fiber | null, //爹是谁
  child: Fiber | null, //孩子是谁
  sibling: Fiber | null, //兄弟是谁
  index: number, //fiber 在兄弟节点中的索引
  ref: //指向在 ReactElement 组件上设置的 ref
    | null
    | (((handle: mixed) => void) & { _stringRef: ?string, ... })
    | RefObject, //指向在 ReactElement 组件上设置的 ref
  pendingProps: any, // 从`ReactElement`对象传入的 props. 用于和`fiber.memoizedProps`比较可以得出属性是否变动
  memoizedProps: any, // 上一次生成子节点时用到的属性,生成子节点之后保持在内存中,用于比较 pendingProps 和 memoizedProps 是否变动
  updateQueue: mixed, // 存储 state 更新的队列,当前节点的 state 改动之后,都会创建一个 update 对象添加到这个队列中。
  memoizedState: any, // 用于输出的 state, 最终渲染所使用的 state
  dependencies: Dependencies | null, // 该 fiber 节点所依赖的 (contexts, events) 等
  mode: TypeOfMode, // 二进制位 Bitfield,继承至父节点,影响本 fiber 节点及其子树中所有节点。与 react 应用的运行模式有关 (有 ConcurrentMode, BlockingMode, NoMode 等选项).

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

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

JSX, ReactElement, Fiber 及 DOM 的关系

  • 根据 JSX 执行 render 后生成 ReactElement
  • 根据 ReactElement 生成 fiber 树 结构比对
  • 根据 fiber 树 生成 DOM

React 的启动

js
ReactDOM.render(<App />, document.getElementById('root'), (dom) => {})
  • legacy 模式:ReactDOM.render(<App />, rootNode) 方法将组件渲染到指定的 DOM 节点上。这种模式不支持并发渲染,因此可能会导致页面卡顿和性能问题。

    • 在没有进入 render 阶段(react-reconciler 包)之前,reactElement() 和 DOM 对象 div#root 之间没有关联。第一步
    • react 初始化 初始化
    • 此时 reactElement(<App/>) 还是独立在外的,还没有和目前创建的 3 个全局对象关联起来 对象关系图
    • 目前 ReactElement, Fiber 及 DOM 三者之间的关系,fiber 树的构造过程就是把 ReactElement 转换为 fiber 树的过程,这个过程中内存中有 2 棵 fiber 树
      • fiberRoot.current 为当前界面的 fiber 树,如果还没有渲染,那么 fiberRoot.current = null
      • 正在构建的 fiber 树,节点为 workInProgress, 挂载到 HostRootFiber.alternage 上,构造完成后,重新渲染页面,最后切换 fiberRoot.current = workInProgress
  • Blocking 模式:ReactDOM.unstable_createBlockingRoot(rootNode).render(<App />),React 会先渲染完整个应用,然后再将其挂载到 DOM 上。这种模式能够提高应用的渲染性能,但也可能会导致首屏渲染时间过长。

  • Concurrent 模式:ReactDOM.createRoot(rootNode).render(<App />),会采用异步渲染的方式,将应用的渲染过程分成多个优先级不同的任务,并根据任务的优先级和可用时间动态地调整任务的执行顺序。这种模式可以提高应用的响应速度和用户体验,但也需要开发者仔细设计应用的架构和组件。

react 初始化时的三个全局对象

  • ReactDOMRoot:属于 react-dom 包,暴露了 render, unmount 方法,通过 ReatDOM。render 方法引导 react 应用的启动
  • fiberRoot:属于 react-reconciler 包,是运行中的全局上下文,保持 fiber 构建过程中所依赖的全局状态,存储fiber构造循环过程的各种状态,react 内部再根据这些值,执行控制逻辑。
  • HostRootFiber:属于 react-reconciler 包,是 React 应用中的第一个 Fiber 对象,是 Fiber 的根节点,类型为 HostRoot

update 与 UpdateQueue 对象

fiber.updateQueue 是一个链式队列 (即使用链表实现的队列存储结构)

js
UpdateQueue<State> = {|
  baseState: State, //基础 state
  firstBaseUpdate: Update<State> | null, // 基础队列的队首
  lastBaseUpdate: Update<State> | null, //基础队列的队尾
  shared: SharedQueue<State>, //共享队列
  effects: Array<Update<State>> | null, //保存有 callback 回调函数的 update 对象,在 commit 之后,会依次调用这里的回调函数。
|};
// 指向即将输入的 update 队列,在 class 组件中调用 setState() 之后,会将新的 update 对象添加到这个队列中来
SharedQueue<State> = {|
  pending: Update<State> | null,
|};

Update<State> = {|
  eventTime: number, // 发起 update 事件的时间 (17.0.2 中作为临时字段,即将移出)
  lane: Lane, // update 所属的优先级

  tag: 0 | 1 | 2 | 3, // UpdateState,ReplaceState,ForceUpdate,CaptureUpdate
  payload: any, // update 对象真正需要更新的数据,可以设置成一个回调函数或者对象。
  callback: (() => mixed) | null, // 回调函数。commit 完成之后会调用。

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

Hook

js
Hook = {|
  memoizedState: any, // 内存状态,用于输出成最终的 fiber 树
  baseState: any, // 基础状态,当 Hook.queue 更新过后,baseState 也会更新。
  baseQueue: Update<any, any> | null, // 基础状态队列,在 reconciler 阶段会辅助状态合并。
  queue: UpdateQueue<any, any> | null, // 指向一个 Update 队列
  next: Hook | null, // 指向该 function 组件的下一个 Hook 对象,使得多个 Hook 之间也构成了一个链表。
|};

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

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

scheduler 包

Task 对象

js
// task 顺序是通过堆排序来实现的,没有 next 属性 (始终保证数组中的第一个 task 对象优先级最高).
newTask = {
  id: taskIdCounter++,
  callback, // 指向 react-reconciler 包所提供的回调函数。
  priorityLevel,
  startTime,
  expirationTime,
  sortIndex: -1 // 控制 task 在队列中的次序,值越小的越靠前。
}