原文链接(格式更好):《4-5 React 源码解析》
预防针:看过 Vue 源码的正常,不看 React 源码的也正常,因为它非常复杂,未必能看懂,所以看懂一部分就行了。
看了 React 源码与写好 React 没啥关系。
这篇文章能带来的是:搞清楚 React 的工作流程,基于 React 的 17 版本源码
引子
<div>
<h2> hello world </h2>
<div>{ text }</div>
{
list.map(item => <ChildItem item={item}></ChildItem>)
}
</div>
问题:list 数据改变时,怎么最快的判断出来如何更新?
答案:React 为了保持运行时的灵活性,一律采用从头(根节点)遍历,然后再跟之前的对比,把有区别的更新下。
疑问:那若有很多 HTML,React 就比较耗性能了吧?是的!
方案:后续 React 的版本就一直在解决这个问题。比如采用了 fiber、使用异步可中断更新策略等等。
React 为什么要用 Fiber?
因为 React 采用的更新策略是从根节点递归遍历,然后再跟之前的对比,把有区别的更新下
然后在 15 版本,采用的是 Stack Reconciler 同步更新机制,由于是同步的,则会存在阻塞,并且还不支持中断
在之后的版本,采用了 Fiber Reconciler 更新机制,支持异步可中断的遍历/更新,若有优先级高的交互(输入、点击等),则中断遍历/更新,先去处理交互,再接着遍历/更新
React 大概的版本区别
- V15 版本,Stack Reconciler:同步更新机制
- 16.9 ~ 17.0.2 版本,Fiber Reconciler:异步可中断更新机制
-
- 但在 17.0.2 里面只是先做了数据结构,但不稳定(可以理解为先吹了一波),有两个模式
-
-
- legacy 模式:可通过 createa 脚手架创建,有 Fiber 的结构,但不会中断,源码文件为 xxx.old.js
- concurrent 模式:需要自己去编译,无法通过 createa 脚手架创建,实现了中断,源码文件为 xxx.new.js
-
- 18 版本,可以理解为 concurrent 模式 ++
React 中的数据结构
v-dom / element
function App() {
return <div className="app">
<h2>hello world</h2>
<div id="list">
<ul>
<li>list 1</li>
<li>list 2</li>
<li>list 3</li>
</ul>
</div>
</div>
}
// 经过 babel 编译后为:
function App() {
return React.createElement('div', { className: 'app'},
React.createElement('h2', null, 'hello world'),
React.createElement('div', { id: 'list' },
React.createElement('ul', null,
React.createElement('li', null, 'list 1'),
React.createElement('li', null, 'list 2'),
React.createElement('li', null, 'list 3')
)
)
)
}
那React.createElement到底是个啥?源码地址:点这个
核心代码:
function ReactElement(type, key, ref, self, source, owner, props) {
const element = {
// This tag allows us to uniquely identify this as a React Element
$$typeof: REACT_ELEMENT_TYPE,
// Built-in properties that belong on the element
type: type,
key: key,
ref: ref,
props: props,
// Record the component responsible for creating this element.
_owner: owner,
};
// ...
return element;
}
export function createElement(type, config, children) {
let propName;
const props = {};
// ⭐️ 将 config 的值放入 props 中
if (config != null) {
// ...
for (propName in config) {
if (
hasOwnProperty.call(config, propName) &&
!RESERVED_PROPS.hasOwnProperty(propName)
) {
props[propName] = config[propName];
}
}
}
// ⭐️ 将 children 的值放入 props.children 中
const childrenLength = arguments.length - 2;
if (childrenLength === 1) {
props.children = children;
} else if (childrenLength > 1) {
const childArray = Array(childrenLength);
for (let i = 0; i < childrenLength; i++) {
childArray[i] = arguments[i + 2];
}
if (__DEV__) {
if (Object.freeze) {
Object.freeze(childArray);
}
}
props.children = childArray;
}
// ...
return ReactElement(
type,
key,
ref,
self,
source,
ReactCurrentOwner.current,
props,
);
}
通过上述的React.createElement源码可得如下结构(vDom):
function App() {
return React.createElement('div', { className: 'app'},
React.createElement('h2', null, 'hello world'),
React.createElement('div', { id: 'list' },
React.createElement('ul', null,
React.createElement('li', null, 'list 1'),
React.createElement('li', null, 'list 2'),
React.createElement('li', null, 'list 3')
)
)
)
}
// 得出如下结构(简版)
const vDom = {
type: "div",
props: {
className: "app",
children: [
{
type: "h2",
props: { children: "hello world" },
},
{
type: "div",
props: {
id: "list",
children: [
{
type: "ul",
props: {
children: [
{
type: "li",
props: { children: "list 1" },
},
{
type: "li",
props: { children: "list 2" },
},
{
type: "li",
props: { children: "list 3" },
},
],
},
},
],
},
},
],
},
};
fiber
本质就是一个链表形式的数据结构,用来表示 v-dom 的,大致结构如下:
FiberNode = {
tag, // 标明 fiber 的类型,如函数组件、类组件等
key, // React 元素的key,用于优化更新
elementType, // 标明 React 元素的类型,即调用 createElement 时的第一个参数。
type, // 标明 DOM 元素 的类型
// 链表形式
return, // 指向父 fiber
child, // 指向子 fiber
sibling // 指向下一个同级 fiber
}
current Fiber、workInProgress Fiber
current Fiber:对应当前页面中真实渲染的 DOM 的结构
workInProgress Fiber:更新时才创建的并已处理好变更的对应到真实 DOM 的虚拟 DOM 的 Fiber 链表
大致的链表结构如下:
React 的双缓存是什么?
双缓存来源于:图形渲染的优化技术
原理为:
- 在双缓存技术中,系统会在内存(通常为显存)中开辟两个区域:
-
- 前缓冲(Front Buffer):这是与显示器相连的缓冲区,当前正在被显示的内容就存储在这里。
- 后缓冲(Back Buffer):这是一个独立于前缓冲的内存区域,应用程序在该区域进行所有的绘图操作。
- 应用程序会在后缓冲中完成所有的图形绘制工作,而不会直接影响到屏幕上显示的内容。
- 当所有需要更新的画面内容都在后缓冲区绘制完成后,系统会执行一次“缓冲区交换”或“页面翻转”操作,迅速将前后缓冲区的角色互换,即将后缓冲区的内容复制到前缓冲区,并使其成为新的可见帧。
- 由于这个交换操作通常是硬件加速且非常快速的,因此用户看到的是一个完整、无闪烁的新帧画面,而不是半成品或者绘制过程中的中间状态。
简单理解为:预加载
React 中的双缓存指的是两个 Fiber 树,一个用于当前展示(current fiber),一个用于更新的(workInprogress fiber),有变化时则创建workInprogress fiber并在其中进行比较、计算等,之后直接一次性用整个workInprogress fiber(或部分) 替换掉整个current fiber(或部分),不用两个逐个比较与 DOM 更新,从而避免了大量的 DOM 操作带来的性能损耗。
React 的核心库
react 库1、提供虚拟 DOM 相关的 API;2、提供用户相关的 API(useState/use* 等)react-dom 库提供操作 DOM 相关的 API
-
- 其中的核心库
react-reconciler通过调和、调度、提交等过程实现渲染
- 其中的核心库
-
-
- 其中核心的为:
-
-
-
-
- ReactFiberBeginWork.js:创建 workInProgressFiber
- ReactFiberCompleteWork.js:基于 workInProgressFiber 创建 EffectList
- ReactFiberCommitWork.js:基于 EffectList 更新界面
-
-
React 的整体流程(V17 版本)
1. 页面代码
function App() {
return <div className="app">
<h2>hello world</h2>
<div id="list">
<ul>
<li>list 1</li>
<li>list 2</li>
<li>list 3</li>
</ul>
</div>
</div>
}
// 入口:ReactDom.render(...)
ReactDom.render(<App />, document.getElementById('root'));
2. 入口:页面的ReactDom.render(...),调用的源码是react-dom 库里面的render函数
// react-dom 源码
function render(
element: React$Element<any>,
container: Container,
callback: ?Function,
){
// ...
return legacyRenderSubtreeIntoContainer( // 渲染下一级的树到 Container 中
null,
element,
container,
false,
callback,
);
}
3. legacyRenderSubtreeIntoContainer:将子树渲染到容器中,首次渲染则调用legacyCreateRootFromDOMContainer
// 作用:将子树渲染到容器中。
// 具体来说,它是在 React 的旧版本和新的 Fiber 架构之间进行兼容性处理的一部分。
function legacyRenderSubtreeIntoContainer(
parentComponent: ?React$Component<any, any>, // 父组件(可能为null)
children: ReactNodeList, // 要渲染的子节点。
container: Container, // 容器,即 DOM 元素或 React 容器
forceHydrate: boolean, // 一个布尔值,决定是否强制进行 hydration(将服务器端渲染的HTML转换为客户端React组件)。
callback: ?Function, // 回调函数,在渲染完成后调用
): React$Component<any, any> | PublicInstance | null {
// ...
// 从给定的容器中获取 _reactRootContainer ,这可能是根容器
let root: RootType = (container._reactRootContainer: any);
let fiberRoot;
if (!root) { // 不存在,即容器是首次渲染:
// ⭐️ Initial mount
root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
container,
forceHydrate,
);
fiberRoot = root._internalRoot;
// ...
unbatchedUpdates(() => {
updateContainer(children, fiberRoot, parentComponent, callback);
});
} else { // 存在,即容器不是首次渲染
fiberRoot = root._internalRoot;
// ...
// Update,更新容器的内容
updateContainer(children, fiberRoot, parentComponent, callback);
}
// 返回根容器的公共实例
return getPublicRootInstance(root);
}
4. legacyCreateRootFromDOMContainer:用于创建 React 的根节点(FiberRoot),核心为createLegacyRoot
function legacyCreateRootFromDOMContainer(
container: Container,
forceHydrate: boolean,
): RootType {
// ...
return createLegacyRoot(
container,
shouldHydrate
? {
hydrate: true,
}
: undefined,
);
}
5. createLegacyRoot:用于创建 React 的根节点(FiberRoot),核心为ReactDOMBlockingRoot
export function createLegacyRoot(
container: Container,
options?: RootOptions,
): RootType {
return new ReactDOMBlockingRoot(container, LegacyRoot, options);
}
6. ReactDOMBlockingRoot:核心调用createRootImpl
function ReactDOMBlockingRoot(
container: Container,
tag: RootTag,
options: void | RootOptions,
) {
this._internalRoot = createRootImpl(container, tag, options);
}
ReactDOMRoot.prototype.render = ReactDOMBlockingRoot.prototype.render = function(){ ... }
ReactDOMRoot.prototype.unmount = ReactDOMBlockingRoot.prototype.unmount = function(): void { ... }
7. createRootImpl:核心调用createContainer
function createRootImpl(
container: Container,
tag: RootTag,
options: void | RootOptions,
) {
// ...
const root = createContainer(container, tag, hydrate, hydrationCallbacks);
// ...
return root;
}
8. createContainer:核心调用createFiberRoot
export function createContainer(
containerInfo: Container,
tag: RootTag,
hydrate: boolean,
hydrationCallbacks: null | SuspenseHydrationCallbacks,
): OpaqueRoot {
return createFiberRoot(containerInfo, tag, hydrate, hydrationCallbacks);
}
9. createFiberRoot:核心调用new FiberRootNode
export function createFiberRoot(
containerInfo: any,
tag: RootTag,
hydrate: boolean,
hydrationCallbacks: null | SuspenseHydrationCallbacks,
): FiberRoot {
const root: FiberRoot = (new FiberRootNode(containerInfo, tag, hydrate): any);
const rootFiber = createHostRootFiber(tag);
root.current = rootFiber; // ⭐️ current Fiber
// ...
return root;
}
10. FiberRootNode:一个构造函数,生成Fiber Root Node的数据结构
function FiberRootNode(containerInfo, tag, hydrate) {
this.tag = tag;
this.containerInfo = containerInfo;
this.pendingChildren = null;
this.current = null;
this.pingCache = null;
this.finishedWork = null;
this.timeoutHandle = noTimeout;
this.context = null;
this.pendingContext = null;
this.hydrate = hydrate;
this.callbackNode = null;
this.callbackPriority = NoLanePriority;
this.eventTimes = createLaneMap(NoLanes);
this.expirationTimes = createLaneMap(NoTimestamp);
this.pendingLanes = NoLanes;
this.suspendedLanes = NoLanes;
this.pingedLanes = NoLanes;
this.expiredLanes = NoLanes;
this.mutableReadLanes = NoLanes;
this.finishedLanes = NoLanes;
this.entangledLanes = NoLanes;
this.entanglements = createLaneMap(NoLanes);
if (supportsHydration) {
this.mutableSourceEagerHydrationData = null;
}
if (enableSchedulerTracing) {
this.interactionThreadID = unstable_getThreadID();
this.memoizedInteractions = new Set();
this.pendingInteractionMap = new Map();
}
if (enableSuspenseCallback) {
this.hydrationCallbacks = null;
}
}
11. 总结一下前面创建Fiber Root的流程:
12. 创建之后,后续关键调用为updateContainer,其中关键为scheduleUpdateOnFiber
export function updateContainer(
element: ReactNodeList,
container: OpaqueRoot,
parentComponent: ?React$Component<any, any>,
callback: ?Function,
): Lane {
// ...
const current = container.current;
// ...
const update = createUpdate(eventTime, lane);
// ...
scheduleUpdateOnFiber(current, lane, eventTime);
return lane;
}
13. scheduleUpdateOnFiber:关键调用performSyncWorkOnRoot
export function scheduleUpdateOnFiber(
fiber: Fiber,
lane: Lane,
eventTime: number,
) {
// ...
// 关键函数:1
const root = markUpdateLaneFromFiberToRoot(fiber, lane);
// ...
if (lane === SyncLane) {
// ...
// 关键函数:2
performSyncWorkOnRoot(root);
} else {
ensureRootIsScheduled(root, eventTime);
schedulePendingInteractions(root, lane);
if (executionContext === NoContext) {
// Flush the synchronous work now, unless we're already working or inside
// a batch. This is intentionally inside scheduleUpdateOnFiber instead of
// scheduleCallbackForFiber to preserve the ability to schedule a callback
// without immediately flushing it. We only do this for user-initiated
// updates, to preserve historical behavior of legacy mode.
resetRenderTimer();
flushSyncCallbackQueue();
}
}
} else {
// Schedule a discrete update but only if it's not Sync.
if (
(executionContext & DiscreteEventContext) !== NoContext &&
// Only updates at user-blocking priority or greater are considered
// discrete, even inside a discrete event.
(priorityLevel === UserBlockingSchedulerPriority ||
priorityLevel === ImmediateSchedulerPriority)
) {
// This is the result of a discrete event. Track the lowest priority
// discrete update per root so we can flush them early, if needed.
if (rootsWithPendingDiscreteUpdates === null) {
rootsWithPendingDiscreteUpdates = new Set([root]);
} else {
rootsWithPendingDiscreteUpdates.add(root);
}
}
// Schedule other updates after in case the callback is sync.
ensureRootIsScheduled(root, eventTime);
schedulePendingInteractions(root, lane);
}
// We use this when assigning a lane for a transition inside
// `requestUpdateLane`. We assume it's the same as the root being updated,
// since in the common case of a single root app it probably is. If it's not
// the same root, then it's not a huge deal, we just might batch more stuff
// together more than necessary.
mostRecentlyUpdatedRoot = root;
}
14. performSyncWorkOnRoot:关键调用renderRootSync、commitRoot
function performSyncWorkOnRoot(root) {
// ...
exitStatus = renderRootSync(root, lanes);
// ...
commitRoot(root);
// ...
return null;
}
15. renderRootSync:关键调用workLoopSync
function renderRootSync(root: FiberRoot, lanes: Lanes) {
// ...
workLoopSync();
// ...
return workInProgressRootExitStatus;
}
16. workLoopSync:递归调用performUnitOfWork
// 一个递归函数,逻辑为根据最开始的页面代码来调用的
function workLoopSync() {
// Already timed out, so perform work without checking if we need to yield.
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
}
// 最开始的页面代码
function App() {
return <div className="app">
<h2>hello world</h2>
<div id="list">
<ul>
<li>list 1</li>
<li>list 2</li>
<li>list 3</li>
</ul>
</div>
</div>
}
// 入口:ReactDom.render(...)
ReactDom.render(<App />, document.getElementById('root'));
则 workLoopSync 调用 performUnitOfWork 时的参数为:
第①次:tag = 3,elementType = null,代表 container
第②次:tag = 2,elementType = f App(),代表 function App() { ...} 函数
第③次:tag = 5,elementType = 'div',代表 <div className="app"> 元素
第④次:tag = 5,elementType = 'h2',代表 <h2> 元素
第⑤次:tag = 5,elementType = 'div',代表 <div id="list"> 元素
...
第n次:tag = 5,elementType = 'li',代表 <li>list 3</li> 元素
17. performUnitOfWork:被递归调用,核心调用beginWork、completeUnitOfWork
function performUnitOfWork(unitOfWork: Fiber): void {
// ...
let next;
// ...
// 调用 beginWork 处理当前元素,返回值为子节点,没有则返回 null
next = beginWork(current, unitOfWork, subtreeRenderLanes);
// ...
if (next === null) {
// 若无子节点,则完成当前任务
completeUnitOfWork(unitOfWork);
} else {
// 否则继续处理子节点
workInProgress = next;
}
ReactCurrentOwner.current = null;
}
所以递归逻辑为:先子级后同级
workLoopSync递归调用performUnitOfWork处理同级,performUnitOfWork调用beginWork处理子级
function App() {
return <div className="app">
<h2>hello world</h2>
<div id="list">
<ul>
<li>list 1</li>
<li>list 2</li>
<li>list 3</li>
</ul>
</div>
</div>
}
a. 递归调用逻辑图(Fiber 链表结构)
上图体现的就是Fiber的数据结构,核心就是:return(父)、child(子)、sibling(兄)
18. beginWork:作用是创建 workInProgressFiber
生成v-dom,然后和current fiber对比,向下调和的过程
就是由 fiberrRoot 按照 child 指针逐层往下调和,期间会执行:函数组件、类组件,DIFF 子节点,从而打上不同的effectTag
function beginWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
): Fiber | null {
const updateLanes = workInProgress.lanes;
// ...
if (current !== null) {
// 初次渲染不会进
// 新老对比
const oldProps = current.memoizedProps;
const newProps = workInProgress.pendingProps;
}
// 匹配节点的 tag 类型,然后调用对应的方法去
switch (workInProgress.tag) {
case IndeterminateComponent: {
return mountIndeterminateComponent(
current,
workInProgress,
workInProgress.type,
renderLanes,
);
}
case LazyComponent: {
const elementType = workInProgress.elementType;
return mountLazyComponent(
current,
workInProgress,
elementType,
updateLanes,
renderLanes,
);
}
// ...
}
}
19. completeUnitOfWork:作用是根据effectTag,创建effectList
调用createInstance创建真实的 DOM,但还不会渲染到页面上
effectList将需要更新的数据形成一个新的 Fiber 链表结构,这样就只需要去更新这些
function completeUnitOfWork(unitOfWork: Fiber): void {
let completedWork = unitOfWork;
do {
const current = completedWork.alternate;
const returnFiber = completedWork.return;
// Check if the work completed or if something threw.
if ((completedWork.flags & Incomplete) === NoFlags) {
// ...
resetCurrentDebugFiberInDEV();
if (next !== null) {
// Completing this fiber spawned new work. Work on that next.
workInProgress = next;
return;
}
} else {
const next = unwindWork(completedWork, subtreeRenderLanes);
// Because this fiber did not complete, don't reset its expiration time.
if (next !== null) {
next.flags &= HostEffectMask;
workInProgress = next;
return;
}
// ...
}
const siblingFiber = completedWork.sibling;
if (siblingFiber !== null) {
// If there is more work to do in this returnFiber, do that next.
workInProgress = siblingFiber;
return;
}
// Otherwise, return to the parent
completedWork = returnFiber;
// Update the next thing we're working on in case something throws.
workInProgress = completedWork;
} while (completedWork !== null);
// We've reached the root.
if (workInProgressRootExitStatus === RootIncomplete) {
workInProgressRootExitStatus = RootCompleted;
}
}
20. 再总结一下前面的调用栈流程:
21. commitRoot
当前面的beginWork、completeWork完成后,后续关键流程为commitWork,核心调用方法commitRoot,它其中核心调用commitRootImpl
function commitRoot(root) {
const renderPriorityLevel = getCurrentPriorityLevel();
runWithPriority(
ImmediateSchedulerPriority,
commitRootImpl.bind(null, root, renderPriorityLevel),
);
return null;
}
22. commitRootImpl
核心调用:flushPassiveEffects、commitBeforeMutationEffects、commitMutationEffects、commitLayoutEffects
function commitRootImpl(root, renderPriorityLevel) {
do {
// ⭐️ 处理一些还未执行完毕的 useEffect
flushPassiveEffects();
} while (rootWithPendingPassiveEffects !== null);
// ...
// ⭐️ 更新前
commitBeforeMutationEffects(finishedWork);
// ...
// ⭐️ 更新时
commitMutationEffects(finishedWork, root, renderPriorityLevel);
if (shouldFireAfterActiveInstanceBlur) {
afterActiveInstanceBlur();
}
resetAfterCommit(root.containerInfo);
// finishedWork = workInProgressFiber
root.current = finishedWork; // ⭐️ 实现双缓存的切换
// ⭐️ 更新后
commitLayoutEffects(root);
// ...
return null;
}
a. flushPassiveEffects
处理一些还未执行完毕的 useEffect
b. commitBeforeMutationEffects(更新前)
调用getSnapshotBeforeUpdate生命周期
c. commitMutationEffects(更新时)
通过 Fiber 链表结构,一层层进行处理增删改
d. commitLayoutEffects(更新后)
进行 Layout(布局)阶段,执行一些生命周期:componentDidMount、componentWillUpdate 等、执行 setState 的 callback、调用 useLayoutEffect、将 useEffect 存储起来
React 的异步可中断(V18 版本)
中断的是下一次的 beginWork
function workLoopConcurrent() {
// Perform work until Scheduler asks us to yield
while (workInProgress !== null && !shouldYield()) {
performUnitOfWork(workInProgress);
}
}
shouldYield意思是交出执行权。
浏览器的任务执行顺序:宏任务 -> 微任务 -> RAF(requestAnimationFrame) -> render -> RIC(requestIdelCallback) -> 新的宏任务 -> 新的微任务 -> ...
当面对长任务时,React 会采用分段执行,但又需要满足可打断,先执行后面的,那可以使用的方案如下:
- setTimeout:宏任务,满足 render 后再执行,但它存在 4ms 的延迟
- Promise:微任务,不满足 render 后再执行,宏任务执行后就立马执行了
- requestIdelCallback:在浏览器空闲时期执行,满足 render 后再执行,但它执行时间不确定,兼容性差
- MessageChannel:浏览器创建的低延迟通信,可以创建一个宏观上的异步操作,与 render 执行无关
仿照 React 源码,模拟实现打断
// 仿照 React 源码,模拟实现打断
// 存放 任务 的队列
const queue = []
// 存放 flush 函数的队列
const transition = []
let deadTime;
const split = 5 // 任务执行超过 5ms,则让出执行权
const now = () => performance.now() // 获取当前时间,计时精度更高
const peek = arr => arr[0] // 拿出数组的第一个值
const poseMessage = (()= >{
const { port1, port2 } = new MessageChannel()
// ⭐️ 当 port2.postMessage 调用后,类似于在 setTimeout 后再执行 port1.ommessgae
// 恢复执行
port1.ommessgae = () => {
// 取出 transition 中的第一个 flush 并执行
peek(transition)()
}
// poseMessage() 执行的是这个 return 的函数
return () => {
port2.postMessage() // 让出执行权
}
})()
function startTransition(flush) {
// 将 flush 放到 transition 数组里面 && 执行 poseMessage()
transition.push(flush) && poseMessage()
}
function shouldYield() {
// 5ms 到了 或者 有更高优先级的操作
return now() >= deadTime
|| navigatior.scheduling.isInputPending // 模拟更高优先级的操作
}
// 时间分片的函数
function flush() {
// 任务执行的截止时间为:当前时间 + 5ms
deadTime = now() + split
const task = peek(queue) // 拿出第一个任务,task = { task }
while(task && !shouldYield()) {
const { task } = task
task.task = null // queue[0] = { task: null }
// 执行任务
const next = task()
// 如果 next 是函数,则任务还未执行完,类似于 workInProgress benginWork 还未完
if(next && typeof next === 'function') {
// 没执行完的,重新放进去
task.task = next // queue[0] = { task: next }
} else {
// 执行完了
queue.shift()
}
}
// 代码执行到这后,就两种情况:
// 1、task 执行完了
// 2、task 还有,但 showYield() 为 true 让出执行权了
task && startTransition(flush)
}
// 用 schedule 来调度任务
function schedule(task) {
queue.push( { task } ) // 将任务放进队列中
startTransition(flush)
}
// 模拟的任务
function myTask() {
return () => {
console.log(1)
return () => {
console.log(2)
return () => {
console.log(3)
return () => {
console.log(4)
}
}
}
}
}
// 将任务放入队列中,开始执行
schedule(myTask)
高优先级操作
- 用户交互事件处理
- 错误边界
- 引发布局或绘制的操作
Suspense 组件
本质是一个错误边界
可以作为组件的异步加载,也可以将异步请求改写为同步逻辑
const fetchList = () => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(["a", "b", "c", "d"]);
}, 1000);
});
};
const readApi = (fn) => {
let state = "pending";
let res;
const suspense = fn()
.then((r) => {
state = "success";
res = r;
})
.catch((err) => {
state = "error";
res = err;
});
function read() {
if (state === "pending") {
throw suspense;
} else if (state === "error") {
throw res;
} else if (state === "success") {
return res;
}
}
return read;
};
const ListApi = readApi(fetchList);
const List = () => {
// const [list, setList] = useState([]);
// useEffect(() => {
// fetchList().then((res) => {
// setList(res);
// });
// }, []);
const list = ListApi();
return (
<div>
<ul>
{list.map((item) => (
<li key={item}>{item}</li>
))}
</ul>
</div>
);
};
export default function FuncComName(props) {
return (
<div>
<Suspense fallback={<div>loading...</div>}>
<List />
</Suspense>
</div>
);
}
// 逻辑解析:当第一次加载 List 组件时,走的是这个逻辑
// if (state === "pending") {
// throw suspense;
// }
// 抛出了 suspense 错误,被 <Suspense 的 fallback 捕获,所以显示 loading...
// 但由于 suspense 是个 Promise,所以等它执行完毕后又重新渲染 List 组件
// 然后走的先这个逻辑
// else if (state === "success") {
// return res;
// }
// 所以最终又正常显示了
简易手写
class MySuspense extends Component {
constructor(props) {
super(props);
this.state = {
loading: false,
};
}
componentDidCatch(error) {
if (typeof error.then === "function") {
this.setState({ loading: true });
error.then(() => this.setState({ loading: false }));
}
}
render() {
return this.state.loading ? this.props.fallback : this.props.children;
}
}
补充
SetTimeout
存在 4ms 的延迟问题
就算设置的是 0,但浏览器可能会因为其他任务(DOM 更新、事件处理等),延迟执行回调函数
所以嵌套调用越多,时间越不准确
MessageChannel
是浏览器提供的一个高级通信机制,它允许在不同上下文之间(比如窗口、iframe 或者 worker 线程)进行安全且异步的消息传递。
适用于在两个窗口之间建立直接的通信通道。
创建 MessageChannel
const messageChannel = new MessageChannel();
// 分别获取两个端口
const port1 = messageChannel.port1;
const port2 = messageChannel.port2;
发送、接受信息
// 在一个端口上接受消息
port1.onmessage = (event) => {
console.log('收到的信息:', event.data)
}
// 在一个端口上发送消息
port1.postMessage('喂喂,收得到吗?')
BroadcastChannel
在相同源的不同上下文之间(包括但不限于标签页)实现双向、异步通信
适用于同一浏览器下的不同窗口进行广播式的通信。
创建 BroadcastChannel
// 创建一个频道名称为 "my_channel" 的 BroadcastChannel
const channel = new BroadcastChannel("my_channel");
发送、接受信息
// 接受消息
channel.onmessage = (event) => {
console.log('收到的信息:', event.data)
}
// 发送消息
channel.postMessage('喂喂,收得到吗?')
面试题
什么是 Fiber?
本质是一个对象形式的数据结构:其中包含了虚拟 DOM、链表、EffectList 等数据
在 React 中存在 currentFiber 与 workInProgressFiber 两个 Fiber,每次的更新通过 currentFiber 与虚拟 DOM 对比去生成 workInProgressFiber,最终用 workInProgressFiber 替换掉 currentFiber,实现双缓存的替换
双缓存步骤:
- 创建 fiberRoot 对象,将所有的 dom 串起来
- 在通过 beginWork 的递归调用,创建出 workInProgressFiber,并打上 EffectTag
- 然后在 completeWork 中,生成 EffectList,并创建真实的 DOM,在更新前执行一些生命周期,在更新时实现双缓存的切换,在更新后执行一些生命周期