React 的更新
只解析到創(chuàng)建更新進(jìn)入調(diào)度器
創(chuàng)建更新的方式
初始渲染
ReactDOM.render
ReactDOM.hydrate更新渲染
setState
forceUpdate (舍棄)
ReactDOM.render
步驟
- 創(chuàng)建 ReactRoot
最頂點(diǎn)的對(duì)象 - 創(chuàng)建 FiberRoot 和 RootFiber
- 創(chuàng)建更新 update
用來(lái)更新調(diào)度, 進(jìn)入調(diào)度后 setState 或 ReactDOM.render 都的調(diào)度器去管理的
初始渲染
ReactDOM.render
ReactDOM.render(<App />, document.getElementById('root'))
<App /> 只是調(diào)用了 createReactElement 生成了ReactElement
ReactDOM.render 把 這個(gè) ReactElement 渲染成 dom tree
初始渲染
在 react-dom/client/ReactDOM.js 文件下, 有 render hydrate 兩個(gè)方法, render 是用在瀏覽器環(huán)境內(nèi)的,hydrate 用在服務(wù)器環(huán)境下,唯一的區(qū)別是調(diào)用 legacyRenderSubtreeIntoContainer 方法傳入的第四個(gè)參數(shù)不同。
const ReactDOM = {
// ...
hydrate(element: React$Node, container: DOMContainer, callback: ?Function) {
// TODO: throw or warn if we couldn't hydrate?
return legacyRenderSubtreeIntoContainer(
null,
element,
container,
true,
callback,
);
},
render(
element: React$Element<any>,
container: DOMContainer,
callback: ?Function,
) {
return legacyRenderSubtreeIntoContainer(
null,
element,
container,
false,
callback,
);
},
// ...
}
- legacyRenderSubtreeIntoContainer 中 container 就是首次渲染傳入的<div id="root">, 這個(gè)dom 肯定不存在 _reactRootContainer 屬性,所以 !root 內(nèi)的就是初次渲染的邏輯
- 而 root 是由首次渲染邏輯時(shí) 由 legacyCreateRootFromDOMContainer 生成的一個(gè) Fiber 對(duì)象。
root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
container,
forceHydrate,
);
function legacyRenderSubtreeIntoContainer(
parentComponent: ?React$Component<any, any>,
children: ReactNodeList,
container: DOMContainer,
forceHydrate: boolean,
callback: ?Function,
) {
// ...
// container 就是首次渲染傳入的<div id="root">, 這個(gè)dom 肯定不存在 _reactRootContainer 屬性,所以 !root 內(nèi)的就是初次渲染的邏輯
let root: Root = (container._reactRootContainer: any);
if (!root) {
// Initial mount 初次渲染,進(jìn)行 new ReactRoot, 創(chuàng)建一個(gè) createFiberRoot 回來(lái)
root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
container,
forceHydrate,
);
if (typeof callback === 'function') {
// 回調(diào)封裝
}
// Initial mount should not be batched. unbatchedUpdates 批量更新
DOMRenderer.unbatchedUpdates(() => {
if (parentComponent != null) { // render, hydrate 傳入的 parentComponent 都是 null
// 批量更新回調(diào), ReactDOM.render 不執(zhí)行
} else {
root.render(children, callback); // 主要執(zhí)行 ReactRoot 實(shí)例的 render
}
});
} else { // 更新渲染的邏輯 }
return DOMRenderer.getPublicRootInstance(root._internalRoot); // _internalRoot 是 new ReactRoot 生成的 Fiber 對(duì)象
}
- legacyCreateRootFromDOMContainer 就是把 root dom, container 內(nèi)的其余節(jié)點(diǎn)清空創(chuàng)建一個(gè) new ReactRoot 實(shí)例
function legacyCreateRootFromDOMContainer(
container: DOMContainer, // ReactDOM.render 傳入的 root
forceHydrate: boolean, // render: false, hydrate: true, SSR 時(shí)進(jìn)行節(jié)點(diǎn)復(fù)用,也是 render hydrate 唯一的區(qū)別
): Root {
const shouldHydrate =
forceHydrate || shouldHydrateDueToLegacyHeuristic(container);
// First clear any existing content.
if (!shouldHydrate) { // false 客戶(hù)端渲染
let warned = false;
let rootSibling;
// 把 root 的所有子節(jié)點(diǎn)刪掉
while ((rootSibling = container.lastChild)) {
container.removeChild(rootSibling);
}
}
// Legacy roots are not async by default. root 節(jié)點(diǎn)創(chuàng)建時(shí)同步的,isConcurrent: false
const isConcurrent = false;
return new ReactRoot(container, isConcurrent, shouldHydrate);
}
- ReactRoot 生成實(shí)例創(chuàng)建了一個(gè) root 實(shí)例屬性,root 由 react-reconcile 模塊包里的 createContainer 方法 調(diào)用 createFiberRoot 生成一個(gè) Fiber 對(duì)象,最后掛載到實(shí)例的 _internalRoot 上
function ReactRoot(
container: Container,
isConcurrent: boolean,
hydrate: boolean,
) {
// 創(chuàng)建一個(gè) createFiberRoot
const root = DOMRenderer.createContainer(container, isConcurrent, hydrate);
this._internalRoot = root;
}
// DOMRenderer.createContainer
export function createContainer(
containerInfo: Container,
isConcurrent: boolean,
hydrate: boolean,
): OpaqueRoot {
return createFiberRoot(containerInfo, isConcurrent, hydrate);
}
- legacyCreateRootFromDOMContainer 最終執(zhí)行的 root.render 就是 new ReactRoot 的原型方法 ReactRoot.prototype.render , 最終是調(diào)用 react-reconcile 模塊包里的 updatecontainer
ReactRoot.prototype.render = function(
children: ReactNodeList,
callback: ?() => mixed,
): Work {
const root = this._internalRoot; // 一個(gè) fiber root
const work = new ReactWork();
callback = callback === undefined ? null : callback;
if (callback !== null) {
work.then(callback);
}
// render 的重點(diǎn)調(diào)用
DOMRenderer.updateContainer(children, root, null, work._onCommit);
return work;
};
- DOMRenderer.updateContainer 中計(jì)算出一個(gè) expirationTime 傳入了 updateContainerAtExpirationTime.
export function updateContainer(
element: ReactNodeList, // App
container: OpaqueRoot, // 一個(gè) fiber root
parentComponent: ?React$Component<any, any>,
callback: ?Function,
): ExpirationTime {
const current = container.current;
const currentTime = requestCurrentTime(); // 創(chuàng)建一個(gè)時(shí)間差
// 計(jì)算出一個(gè)時(shí)間,ConcurrentMode 會(huì)用到
const expirationTime = computeExpirationForFiber(currentTime, current);
// 主要執(zhí)行
return updateContainerAtExpirationTime(
element,
container,
parentComponent,
expirationTime,
callback,
);
}
- updateContainerAtExpirationTime 中調(diào)用 scheduleRootUpdate
export function updateContainerAtExpirationTime(
element: ReactNodeList,
container: OpaqueRoot,
parentComponent: ?React$Component<any, any>,
expirationTime: ExpirationTime,
callback: ?Function,
) {
// TODO: If this is a nested container, this won't be the root.
const current = container.current;
const context = getContextForSubtree(parentComponent);
if (container.context === null) {
container.context = context;
} else {
container.pendingContext = context;
}
return scheduleRootUpdate(current, element, expirationTime, callback);
}
- scheduleRootUpdate 中
- 使用 createUpdate 創(chuàng)建 update 來(lái)標(biāo)記 react 需要更新的點(diǎn)
- 設(shè)置完 update 屬性再調(diào)用 enqueueUpdate 把 update 放入更新隊(duì)列里
react 更新會(huì)在一個(gè)節(jié)點(diǎn)上整體進(jìn)行很多個(gè)更新,這個(gè)更新 queue 就是管理多次更新的作用- 最后執(zhí)行 scheduleWork 通知 react 進(jìn)行調(diào)度,根據(jù)任務(wù)的優(yōu)先級(jí)進(jìn)行更新。
function scheduleRootUpdate(
current: Fiber,
element: ReactNodeList,
expirationTime: ExpirationTime,
callback: ?Function,
) {
const update = createUpdate(expirationTime); // 用來(lái)標(biāo)記 react 更新的節(jié)點(diǎn)
// Caution: React DevTools currently depends on this property
// being called "element".
update.payload = {element};
callback = callback === undefined ? null : callback;
if (callback !== null) {
warningWithoutStack(
typeof callback === 'function',
'render(...): Expected the last optional `callback` argument to be a ' +
'function. Instead received: %s.',
callback,
);
update.callback = callback;
}
// 把更新的對(duì)象加入到 fiber 對(duì)象上的 updateQueue 里, 會(huì)有很多更新在一個(gè)節(jié)點(diǎn)上產(chǎn)生
enqueueUpdate(current, update);
// 開(kāi)始進(jìn)行調(diào)度
scheduleWork(current, expirationTime);
return expirationTime;
}
ReactDOM.render 階段總結(jié)
- 初次渲染 傳入 APP 組件和 getElementById(root) 執(zhí)行 ReactDOM.render
- ReactDOM.render 返回并執(zhí)行 legacyRenderSubtreeIntoContainer
- legacyRenderSubtreeIntoContainer 內(nèi)調(diào)用 legacyCreateRootFromDOMContainer 把返回值掛載到 root 節(jié)點(diǎn)的 _reactRootContainer 屬性上
- 而 legacyCreateRootFromDOMContainer 把 getElementById(root) 里的子節(jié)點(diǎn)清空,創(chuàng)建并返回 new ReactRoot 給 getElementById(root) 的 _reactRootContainer 屬性上
- ReactRoot 生成實(shí)例時(shí)調(diào)用 react-reconcile 模塊的 createContainer 傳入 getElementById(root) 執(zhí)行 createFiberRoot 生成一個(gè) FiberRoot 對(duì)象掛載到實(shí)例的 _internalRoot
- legacyRenderSubtreeIntoContainer 最終調(diào)用 上面生成的 ReactRoot 實(shí)例的 ReactRoot.prototype.render 原型方法
- ReactRoot.prototype.render 把子節(jié)點(diǎn)和實(shí)例生成的 _internalRoot Fiber 對(duì)象傳入 react-reconcile 模塊的 updateContainer 中
- 在 updateContainer 中 react 計(jì)算出一個(gè) expirationTime 傳入 updateContainerAtExpirationTime 調(diào)用 scheduleRootUpdate 中做三件事
1 使用 createUpdate 創(chuàng)建 update 來(lái)標(biāo)記 react 需要更新的點(diǎn)
2 設(shè)置完 update 屬性再調(diào)用 enqueueUpdate 把 update 放入當(dāng)前節(jié)點(diǎn)樹(shù)整體的更新隊(duì)列里
3 最后執(zhí)行 scheduleWork 通知 react 進(jìn)行調(diào)度,根據(jù)任務(wù)的優(yōu)先級(jí)進(jìn)行更新。
- 在 updateContainer 中 react 計(jì)算出一個(gè) expirationTime 傳入 updateContainerAtExpirationTime 調(diào)用 scheduleRootUpdate 中做三件事
- ReactRoot.prototype.render 把子節(jié)點(diǎn)和實(shí)例生成的 _internalRoot Fiber 對(duì)象傳入 react-reconcile 模塊的 updateContainer 中
- legacyRenderSubtreeIntoContainer 內(nèi)調(diào)用 legacyCreateRootFromDOMContainer 把返回值掛載到 root 節(jié)點(diǎn)的 _reactRootContainer 屬性上
- ReactDOM.render 此時(shí)
- 創(chuàng)建了一個(gè) ReactRoot 對(duì)象掛載到 getElementById(root) 的 _reactRootContainer 屬性上
- 同時(shí) 在 ReactRoot 實(shí)例 _internalRoot 屬性上生成了 Fiber 對(duì)象
- 調(diào)用 ReactRoot.prototype.render 執(zhí)行 react-reconcile 模塊的 updateContainer 計(jì)算 expirationTime,通過(guò) expirationTime 來(lái)創(chuàng)建 update 對(duì)象,推入 updateQueue 內(nèi),最后根據(jù)優(yōu)先級(jí)進(jìn)行調(diào)度。
FiberRoot
- 整個(gè)應(yīng)用的起點(diǎn)
- 包含了傳入的 getElementById(root)
- 記錄整個(gè)應(yīng)用更新過(guò)程的各種信息 containerInfo (包含了 root 節(jié)點(diǎn)等信息)
FiberRoot 對(duì)象結(jié)構(gòu)
FiberRoot 是 ReactRoot 生成實(shí)例時(shí)調(diào)用 react-reconcile 模塊的 createContainer 傳入 getElementById(root) 執(zhí)行 createFiberRoot 生成一個(gè) FiberRoot 對(duì)象掛載到實(shí)例的 _internalRoot
export function createFiberRoot(
containerInfo: any,
isConcurrent: boolean,
hydrate: boolean,
): FiberRoot {
const uninitializedFiber = createHostRootFiber(isConcurrent);
return root = ({
current: uninitializedFiber, // Fiber 對(duì)象通過(guò) new Fiber 創(chuàng)建,與 ReactElement 對(duì)應(yīng)也是樹(shù)狀結(jié)構(gòu),這里樹(shù)的頂點(diǎn)
containerInfo: containerInfo, // 通過(guò) render 方法傳入的 root 節(jié)點(diǎn), 應(yīng)用掛載的節(jié)點(diǎn)
pendingChildren: null, // ssr 用來(lái)復(fù)用節(jié)點(diǎn)的
// 任務(wù)調(diào)度的時(shí)間標(biāo)記優(yōu)先級(jí)
earliestPendingTime: NoWork,
latestPendingTime: NoWork,
earliestSuspendedTime: NoWork,
latestSuspendedTime: NoWork,
latestPingedTime: NoWork,
// 標(biāo)記渲染過(guò)程中是否有錯(cuò)誤
didError: false,
// 正在等待提交的任務(wù)
pendingCommitExpirationTime: NoWork,
finishedWork: null, // 記錄一次更新渲染過(guò)程完成的任務(wù) Fiber 對(duì)象
timeoutHandle: noTimeout,
context: null,
pendingContext: null,
hydrate,
nextExpirationTimeToWorkOn: NoWork, // 標(biāo)記此次更新要執(zhí)行的是哪個(gè)優(yōu)先級(jí)的任務(wù)
expirationTime: NoWork, // 用在調(diào)度中
firstBatch: null,
nextScheduledRoot: null, // 鏈表屬性
interactionThreadID: unstable_getThreadID(),
memoizedInteractions: new Set(),
pendingInteractionMap: new Map(),
}: FiberRoot);
}
Fiber
- FiberRoot.current 就是一個(gè) Fiber 對(duì)象
- 每一個(gè) ReactElement 對(duì)應(yīng)一個(gè) Fiber 對(duì)象
- 記錄節(jié)點(diǎn)的各種狀態(tài)
class 組件的 this.state、this.props ,在 Fiber 更新后才會(huì)更新 class 組件上的 this.state, props,也是 hooks 實(shí)現(xiàn)的原理,function 組件是沒(méi)有 this.state this.props 的,F(xiàn)iber 有能力記錄這些狀態(tài)之后在 function 組件更新后拿到這些狀態(tài)。 - 串聯(lián)整個(gè)應(yīng)用形成的樹(shù)結(jié)構(gòu)
ReactElement 對(duì)應(yīng)的結(jié)構(gòu)
Fiber 的數(shù)據(jù)結(jié)構(gòu)
FiberNode 有三個(gè)屬性 return、child、sibling, 分別代表此 Fiber 對(duì)象的父節(jié)點(diǎn),第一個(gè)子節(jié)點(diǎn),自己的兄弟節(jié)點(diǎn)
function FiberNode(
tag: WorkTag,
pendingProps: mixed,
key: null | string,
mode: TypeOfMode,
) {
// Instance
this.tag = tag; // 標(biāo)記不同的組件類(lèi)型,不同的更新方式
this.key = key; // key
this.elementType = null; // createElement 第一個(gè)參數(shù),組件 或者 標(biāo)簽
this.type = null; // 記錄異步組件 resolved 后是 class 還是 function 組件
this.stateNode = null; // 節(jié)點(diǎn)的實(shí)例,對(duì)應(yīng) class 組件或者 dom 節(jié)點(diǎn),function 沒(méi)有實(shí)例就沒(méi) stateNode
// Fiber
this.return = null; // 父親節(jié)點(diǎn)
this.child = null; // 第一個(gè)子節(jié)點(diǎn)
this.sibling = null; // 自己的下一個(gè)兄弟節(jié)點(diǎn)
this.index = 0;
this.ref = null; // ref
this.pendingProps = pendingProps; // 每個(gè)創(chuàng)建的新 props
this.memoizedProps = null; // 老 props
this.updateQueue = null; // 節(jié)點(diǎn)創(chuàng)建的 update 對(duì)象 queue
this.memoizedState = null; // 老 state,新 state 是由 updateQueue 計(jì)算出來(lái)的然后覆蓋這里
this.firstContextDependency = null; // context 相關(guān)
this.mode = mode; // 標(biāo)記時(shí)創(chuàng)建,繼承父節(jié)點(diǎn) mod
// Effects 副作用
this.effectTag = NoEffect;
this.nextEffect = null;
this.firstEffect = null;
this.lastEffect = null;
this.expirationTime = NoWork; // 任務(wù)的過(guò)期時(shí)間
this.childExpirationTime = NoWork; // 子節(jié)點(diǎn)更新的過(guò)期時(shí)間
this.alternate = null; // Fiber 用來(lái)復(fù)制復(fù)用 Fiber 的。
if (enableProfilerTimer) {
this.actualDuration = 0;
this.actualStartTime = -1;
this.selfBaseDuration = 0;
this.treeBaseDuration = 0;
}
}
Fiber 對(duì)象對(duì)應(yīng)的結(jié)構(gòu)
update 和 updateQueue
- 用于記錄組件狀態(tài)的改變
記錄改變的方式和內(nèi)容 - 存放在 updateQueue
存放多個(gè) update 用來(lái)計(jì)算出最終改變的結(jié)果 - 多個(gè) update 可以同時(shí)存在
setState 三次會(huì)創(chuàng)建三個(gè)update,放到 updateQueue 里
update 結(jié)構(gòu)
在 legacyRenderSubtreeIntoContainer 最終調(diào)用 ReactRoot.prototype.render 時(shí)執(zhí)行 scheduleRootUpdate 里執(zhí)行 createUpdate
export function createUpdate(expirationTime: ExpirationTime): Update<*> {
return {
expirationTime: expirationTime, // 當(dāng)前更新的過(guò)期時(shí)間
tag: UpdateState, // 四個(gè)狀態(tài),更新updateState 0、替換replaceState 1、強(qiáng)制forceUpdate 2、throw 捕獲 captureUpdate 3
payload: null, // 實(shí)際操作內(nèi)容,在外面賦值上去 update.payload = { element } 初次渲染傳入的是元素,setState 可能傳入的就是對(duì)象或者方法
callback: null,
next: null, // 下一個(gè) update 單向鏈表
nextEffect: null,
};
}
updateQueue 結(jié)構(gòu)
export function createUpdateQueue<State>(baseState: State): UpdateQueue<State> {
const queue: UpdateQueue<State> = {
baseState, // 每次操作更新之后計(jì)算出的 state,作為下一次計(jì)算的基礎(chǔ)
firstUpdate: null, // 記錄鏈表結(jié)構(gòu)
lastUpdate: null, // 記錄鏈表結(jié)構(gòu)
firstCapturedUpdate: null, // 記錄鏈表結(jié)構(gòu)
lastCapturedUpdate: null, // 記錄鏈表結(jié)構(gòu)
firstEffect: null,
lastEffect: null,
firstCapturedEffect: null,
lastCapturedEffect: null,
};
return queue;
}
enqueueUpdated 方法
enqueueUpdated 就是在 fiber 對(duì)象上創(chuàng)建一個(gè) updateQueue,然后把 update 對(duì)象傳入到這個(gè) queue 里
export function enqueueUpdate<State>(fiber: Fiber, update: Update<State>) {
// ...
let queue1
queue1 = fiber.updateQueue = createUpdateQueue(fiber.memoizedState);
appendUpdateToQueue(queue1, update);
}
function appendUpdateToQueue<State>(
queue: UpdateQueue<State>,
update: Update<State>,
) {
// Append the update to the end of the list.
if (queue.lastUpdate === null) {
// Queue is empty
queue.firstUpdate = queue.lastUpdate = update;
} else {
queue.lastUpdate.next = update;
queue.lastUpdate = update;
}
}
expirationTime
初次渲染最后執(zhí)行 ReactDOM.render 調(diào)用 ReactRoot.prototype.render 中調(diào)用 react-reconcile 模塊的 updateContainer 會(huì)計(jì)算一個(gè) expirationTime 然后用這個(gè)時(shí)間創(chuàng)建 update 對(duì)象推入 updateQueue 內(nèi)
expirationTime 的計(jì)算
export function updateContainer(
element: ReactNodeList, // App
container: OpaqueRoot, // 一個(gè) fiber root
parentComponent: ?React$Component<any, any>, // null
callback: ?Function,
): ExpirationTime {
const current = container.current; // Fiber
const currentTime = requestCurrentTime(); // 創(chuàng)建一個(gè)時(shí)間差
// 計(jì)算出一個(gè)時(shí)間,ConcurrentMode 會(huì)用到, 計(jì)算出的是優(yōu)先級(jí)時(shí)間
const expirationTime = computeExpirationForFiber(currentTime, current);
// 主要執(zhí)行
return updateContainerAtExpirationTime(
element,
container,
parentComponent,
expirationTime,
callback,
);
}
- requestCurrentTime 方法返回一個(gè)固定的常量,調(diào)用 recomputeCurrentRendererTime 返回當(dāng)前渲染時(shí)間直到 js 加載初的時(shí)間差值,差值小的值會(huì)在 msToExpirationTime 被計(jì)算成同一個(gè)常數(shù)
function requestCurrentTime() {
// ...
if ( // 沒(méi)有調(diào)度時(shí)間,初次渲染
nextFlushedExpirationTime === NoWork ||
nextFlushedExpirationTime === Never
) {
// If there's no pending work, or if the pending work is offscreen, we can
// read the current time without risk of tearing.
recomputeCurrentRendererTime();
currentSchedulerTime = currentRendererTime;
return currentSchedulerTime;
}
// ...
}
function recomputeCurrentRendererTime() {
const currentTimeMs = now() - originalStartTimeMs; // js 加載到現(xiàn)在渲染的固定值
currentRendererTime = msToExpirationTime(currentTimeMs);
}
export function msToExpirationTime(ms: number): ExpirationTime {
// Always add an offset so that we don't clash with the magic number for NoWork.
return ((ms / UNIT_SIZE) | 0) + MAGIC_NUMBER_OFFSET;
}
- computeExpirationForFiber 方法會(huì)根據(jù)當(dāng)前渲染的 currentTime 計(jì)算出一個(gè)優(yōu)先級(jí)時(shí)間。
function computeExpirationForFiber(currentTime: ExpirationTime, fiber: Fiber) {
let expirationTime;
// ...
if (fiber.mode & ConcurrentMode) { // 異步 mode 才計(jì)算 expirationTime
if (isBatchingInteractiveUpdates) {
// This is an interactive update
expirationTime = computeInteractiveExpiration(currentTime);
} else {
// This is an async update
expirationTime = computeAsyncExpiration(currentTime);
}
}
// ...
return expirationTime;
}
- computeExpirationForFiber 的核心就是根據(jù)渲染方式的 mod 不同來(lái)創(chuàng)建不同優(yōu)先級(jí)的 expirationTime,區(qū)別就在于傳入 computeExpirationBucket 的參數(shù)不同
- 最終的公式
((((currentTime - 2 + 5000 / 10) / 25) | 0) + 1) * 25, 公式用了| 0取整,使得在 * 25 倍數(shù)之間很短的一段時(shí)間粒度里計(jì)算出的值是一樣的 - 在很短時(shí)間內(nèi) setState 多次調(diào)用更新時(shí),也可以保證是同一優(yōu)先級(jí)的更新。
export function computeAsyncExpiration(
currentTime: ExpirationTime,
): ExpirationTime {
return computeExpirationBucket(
currentTime,
LOW_PRIORITY_EXPIRATION,
LOW_PRIORITY_BATCH_SIZE,
);
}
export function computeInteractiveExpiration(currentTime: ExpirationTime) {
return computeExpirationBucket(
currentTime,
HIGH_PRIORITY_EXPIRATION,
HIGH_PRIORITY_BATCH_SIZE,
);
}
不同的 expirationTime
Sync 模式 優(yōu)先級(jí)高
Async 模式 會(huì)進(jìn)行調(diào)度可能會(huì)被中斷,會(huì)計(jì)算出 expirationTime 來(lái)分配優(yōu)先級(jí)
指定 context
同步更新的 expirationTime
- computeExpirationForFiber 在異步 mode 的情況下才根據(jù) currentTime 來(lái)計(jì)算 expirationTime
- expirationTime 等于 Sync 1, NoWork 0,還有就是計(jì)算出來(lái)的時(shí)間值
- 在 expirationContext 不為 NoWork 時(shí),expirationContext 會(huì)根據(jù)更新 api 方式的不同設(shè)置為 Sync 或者 計(jì)算出 Async 方式的優(yōu)先級(jí)時(shí)間
function computeExpirationForFiber(currentTime: ExpirationTime, fiber: Fiber) {
let expirationTime;
if (expirationContext !== NoWork) {
// An explicit expiration context was set;
expirationTime = expirationContext;
} else if (isWorking) {
if (isCommitting) {
// Updates that occur during the commit phase should have sync priority
// by default.
expirationTime = Sync;
} else {
// Updates during the render phase should expire at the same time as
// the work that is being rendered.
expirationTime = nextRenderExpirationTime;
}
} else {
// No explicit expiration context was set, and we're not currently
// performing work. Calculate a new expiration time.
if (fiber.mode & ConcurrentMode) {
// 異步邏輯
} else {
// This is a sync update
expirationTime = Sync;
}
}
return expirationTime;
}
setState 和 forceUpdate
- 給節(jié)點(diǎn)的 Fiber 創(chuàng)建更新
- 更新的類(lèi)型不同
來(lái)源
都是 Component 組件上的原型方法, 他們調(diào)用的 enqueueSetState 等方法都是由不同平臺(tái)創(chuàng)建的 updater 對(duì)象上的,瀏覽器中的 updater 對(duì)象來(lái)自 ReactFiberClassComponent.js 里的 classComponentUpdater 對(duì)象上
function Component(props, context, updater) {
this.props = props;
this.context = context;
this.refs = emptyObject;
// 作為參數(shù)讓不同的平臺(tái)來(lái)控制屬性更新的方式
this.updater = updater || ReactNoopUpdateQueue;
}
Component.prototype.setState = function(partialState, callback) {
// 僅僅調(diào)用了 updater 上的方法 updater 是初始化的第三個(gè)參數(shù)的實(shí)例屬性,跟平臺(tái)相關(guān)
this.updater.enqueueSetState(this, partialState, callback, 'setState');
};
Component.prototype.forceUpdate = function(callback) {
this.updater.enqueueForceUpdate(this, callback, 'forceUpdate');
};
classComponentUpdater
- enqueueSetState, enqueueForceUpdate 幾乎是相同的,在這里我們只看 enqueueSetState
1 從 Map 對(duì)象中獲取 Fiber 對(duì)象
2 創(chuàng)建當(dāng)前時(shí)間
3 優(yōu)先級(jí)時(shí)間
4 創(chuàng)建 update
5 設(shè)置payload
6 執(zhí)行回調(diào)
7 把 update 對(duì)象推入 Fiber 對(duì)象的 updateQueue 內(nèi)
8 進(jìn)行調(diào)度 - 發(fā)現(xiàn) enqueueSetState 所執(zhí)行的順序跟 ReactFiberReconclier.js 的 updateContainer 幾乎是一模一樣的
1 Fiber 對(duì)象是參數(shù)傳進(jìn)來(lái)的
2 payload 是創(chuàng)建 update 對(duì)象后在外面賦值的
3 最后也是創(chuàng)建 update 進(jìn)入 Fiber 對(duì)象的 updateQueue 再進(jìn)行調(diào)度
const classComponentUpdater = {
isMounted,
enqueueSetState(inst, payload, callback) {
const fiber = ReactInstanceMap.get(inst); // 從 Map 對(duì)象中獲取 Fiber 對(duì)象
const currentTime = requestCurrentTime(); // 創(chuàng)建當(dāng)前時(shí)間
const expirationTime = computeExpirationForFiber(currentTime, fiber); // 優(yōu)先級(jí)時(shí)間
const update = createUpdate(expirationTime); // 創(chuàng)建 update
update.payload = payload; // 設(shè)置payload
if (callback !== undefined && callback !== null) {
update.callback = callback; // 執(zhí)行回調(diào)
}
enqueueUpdate(fiber, update); // 把 update 對(duì)象推入 Fiber 對(duì)象的 updateQueue 內(nèi)
scheduleWork(fiber, expirationTime); // 進(jìn)行調(diào)度
},
enqueueReplaceState(inst, payload, callback) {
// ...
},
enqueueForceUpdate(inst, callback) {
// ...
}
};

