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 在队列中的次序, 值越小的越靠前.
}