React宏观设计理念
核心包及架构分层
各司其职。
-
基础包(士兵):react
react 基础包, 只提供定义 react 组件(
ReactElement
)的必要函数, 一般来说需要和渲染器(react-dom
,react-native
)一同使用. 在编写react
应用的代码时, 大部分都是调用此包的 api.
-
渲染器: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
-
协调器(外交官):react-reconciler
react 得以运行的核心包,核心是处理React的diff算法,通过对比fiber tree前后的差异来决定DOM需要做哪些更新。react各大包中的交际花(综合协调
react-dom
,react
,scheduler
各包之间的调用与配合). 管理 react 应用状态的输入和结果的输出. 将输入信号最终转换成输出信号传递给渲染器.
- 输入:(
scheduleUpdateOnFiber
), 将fiber
树生成逻辑封装到一个回调函数中(涉及fiber
树形结构,fiber.updateQueue
队列, 调和算法等),- 把此回调函数(
performSyncWorkOnRoot
或performConcurrentWorkOnRoot
)送入scheduler
进行调度scheduler
会控制回调函数执行的时机, 回调函数执行完成后得到全新的 fiber 树- 再调用渲染器(如
react-dom
,react-native
等)将 fiber 树形结构最终反映到界面上相较于低版本的stack reconcile的优势:
- 能够将可中断的工作分成块。
- 能够对工作进行优先级排序、重置和重复利用。
- 能够在 React 中在父组件和子组件之间来回传递,以支持布局。
- 能够从 render() 中返回多个元素。
- 更好地支持错误边界。
算法核心假设:
- 不同类型的两个元素将产生不同的树。
- 开发人员可以通过 key 属性暗示哪些子元素可能在不同的渲染中保持稳定。
-
调度器(将军):scheduler
任务调度中枢,核心任务是管理任务优先级,将由
react-reconciler
送入的回调函数,维护一个任务队列,根据优先级等决定其执行时机,并执行。在concurrent
模式下可以实现任务分片.
- 核心任务就是执行回调(回调函数由
react-reconciler
提供)- 通过控制回调函数的执行时机, 来达到任务分片的目的, 实现可中断渲染(
concurrent
模式下才有此特性)
架构模块关系图
备注:玫红色输入,绿色输出
两大工作循环workLoop
-
fiber构造循环
:
数据结构为树。从上至下执行深度优先遍历父-子-兄弟,直到回溯到根节点。
源码位于ReactFiberWorkLoop.js
, 控制 fiber 树的构造, 整个过程是一个深度优先遍历.
-
任务调度循环
:
数据结构为二叉树小堆树。循环执行堆
的顶点, 直到堆
被清空
源码位于Scheduler.js
, 它是react
应用得以运行的保证, 它需要循环调用, 控制所有任务(task
)的调度.
这两个循环对应的 js 源码不同于其他闭包(运行时就是闭包), 其中定义的全局变量, 不仅是该作用域的私有变量, 更用于控制react应用的执行过程
.
三大优先级管理
reconciler
从输入到输出一共经历了 4 个阶段, 在每个阶段中都会涉及到与优先级
相关的处理. 正是通过优先级
的灵活运用, React
实现了可中断渲染
,时间切片(time slicing)
,异步渲染(suspense)
等特性.
-
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中的重点对象
-
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,
};
-
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
-
双缓冲技术Double buffering:
fiberRoot.current
和workInProgress
- 构造过程中:
fiberRoot.current
指向当前界面对应的fiber
树.
- 新的fiber树
workInProgress
构造完成并渲染, 切换fiberRoot.current
指针, 使其继续指向当前界面对应的fiber
树(原来代表界面的 fiber 树, 变成了内存中).
reconciler -协调器 (TODO)
Reconciler负责比较组件树的差异(即diffing过程),并计算出实际DOM需要进行的最小更新。在Fiber架构中,Reconciler可以在必要时暂停、中断和恢复工作。
4个阶段:
- 输入阶段: 衔接
react-dom
包, 承接fiber更新
请求(参考React 应用的启动过程). - 注册调度任务: 与调度中心(
scheduler
包)交互, 注册调度任务task
, 等待任务回调(参考React 调度原理(scheduler)). - 执行任务回调构建阶段: 在内存中构造出
fiber树
和DOM
对象(参考fiber 树构造(初次创建)和 fiber 树构造(对比更新)).异步执行,可中断和恢复。 - commit输出阶段: 与渲染器(
react-dom
)交互, 渲染DOM
节点. 同步执行,不可中断:
-
输入
-
注册调度任务
-
执行任务回调
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;
-
初次创建Fiber树
深度优先遍历:父-子-兄弟
深度优先遍历中,每个节点会经历两个阶段:
-
探寻阶段
beginWork
: 创建fiber节点- 根据
ReactElement
对象创建所有的fiber
节点, 最终构造出fiber树形结构
(设置return
和sibling
指针) - 设置
fiber.flags
: 二进制形式变量, 用来标记fiber
节点 的增,删,改
状态, 等待completeWork阶段处理
- 设置
fiber.stateNode
局部状态: 如Class类型
节点:fiber.stateNode=new Class()
- 设置
fiber.memorizedState
<-fiber.pendingProps, fiber.updateQueue
- 根据
-
回溯阶段
completeWork
:处理上面beginWork
阶段已经创建出来的fiber
节点(completeUnitOfWork(unitOfWork))- 生成DOM:创建当前fiber节点的DOM实例;设置
fiber.StateNode
属性,指向DOM实例对象;设置DOM对象的属性, 绑定事件等 - 更新副作用队列:把当前
fiber
对象的副作用队列(firstEffect
和lastEffect
)添加到父节点的副作用队列之后, 更新父节点的firstEffect
和lastEffect
指针. - 识别处理
fiber.flags
:判断当前fiber
是否有副作用(增,删,改), 如果有, 需要将当前fiber
加入到父节点的effects
队列, 等待commit
阶段处理.
- 生成DOM:创建当前fiber节点的DOM实例;设置
-
更新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;
-
输出 - 渲染
关键方法:commitRoot -> commitRootImpl
在commitRoot
中同时使用到了渲染优先级
和调度优先级
4.1 渲染前:
4.2 渲染
任务:
- 处理副作用队列: 副作用队列在根节点, 即
HostRootFiber
节点。根据fiber.flags分别处理 - 调用渲染器,将最新的 DOM 节点渲染到界面上。已经在内存中, 只是还没渲染,放在首个
HostComponent
类型的fiber
节点的stateNode.
scheduler - 调度器(TODO)
主要职责:
关键函数:
关键文件:
两个任务队列:taskQueue, timerQueue.
React 采用“拉”的方法,只有在必要时才会延迟计算。React 不是一般的数据处理库。它是用来构建用户界面的库。它在应用程序中独具优势,能够知道哪些计算是当前相关的,哪些是不相关的。
taskQueue对象
数据结构是小顶堆数组,通过sortIndex排序,始终保证数组中的第一个task
对象优先级最高。
sortIndex:
- startTime > currentTime还未到期的任务:sortIndex=startTime
- 否则:sortIndex = expirationTime
export opaque type Task = {
id: number,
callback: Callback | null,
priorityLevel: PriorityLevel,
startTime: number,
expirationTime: number,
sortIndex: number, // 制 task 在队列中的次序, 值越小的越靠前。
isQueued?: boolean,
};