核心包
对于 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队列, 调和算法等),把此回调函数(
performSyncWorkOnRoot或performConcurrentWorkOnRoot)送入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 运行的主干逻辑进行概括:
输入: 将每一次更新(如: 新增, 删除, 修改节点之后)视为一次
更新需求(目的是要更新DOM节点).注册调度任务:
react-reconciler收到更新需求之后, 并不会立即构造fiber树, 而是去调度中心scheduler注册一个新任务task, 即把更新需求转换成一个task.执行调度任务(输出): 调度中心
scheduler通过任务调度循环来执行task(task的执行过程又回到了react-reconciler包中).
fiber构造循环是task的实现环节之一, 循环完成之后会构造出最新的 fiber 树.
commitRoot是task的实现环节之二, 把最新的 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
}key属性在reconciler阶段会用到, 目前只需要知道所有的ReactElement对象都有 key 属性(且其默认值是 null, 这点十分重要, 在 diff 算法中会使用到).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函数的第一个参数将作为创建ReactElement的type. 可以看到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, // 生成子树所消耗的时间的总和
|};

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,
|};UpdateQueuebaseState: 表示此队列的基础 statefirstBaseUpdate: 指向基础队列的队首lastBaseUpdate: 指向基础队列的队尾shared: 共享队列effects: 用于保存有callback回调函数的 update 对象, 在commit之后, 会依次调用这里的回调函数.
SharedQueuepending: 指向即将输入的update队列. 在class组件中调用setState()之后, 会将新的 update 对象添加到这个队列中来.
UpdateeventTime: 发起update事件的时间(17.0.2 中作为临时字段, 即将移出)lane:update所属的优先级tag: 表示update种类, 共 4 种.UpdateState,ReplaceState,ForceUpdate,CaptureUpdatepayload: 载荷,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 在队列中的次序, 值越小的越靠前.