reactソースは3 udateとudateQueを読みます.
57734 ワード
react-domのフォローアップコンタイナー部分です.Reactパッケージのソースコードのバージョンを読む16.8.6です.
前の章で
この章は
今ソースに戻ります.
の3つの
私たちは
関数
実は
この章は
この章で掘り起こした二つの大きな穴の一つは
前の章で
react-dom
関数の論理は、入ってきたReactコンポーネントにrender
オブジェクトを作成し、それがアプリケーション全体の起点であることを識別するために使用され、多くのアプリケーション更新関連の表示符を持っている.その後、対応するfiberRoot
をfiber
ノードに作成し、fiberRoot
オブジェクトは、各fiber
が所有するノードであり、更新時間のいくつかの情報、propsおよびstateのいくつかの情報、および関連するノード情報を識別している.ReactElement
は、一方向リンクのデータ構造によって互いに連結されている.この章は
ReactElement
の関数でlegacyRenderSubtreeIntoContainer
の関連対象の部分を作成しました.次にfiber
の関連するロジックを調べます.まずこの部分のコードを振り返ってみます.function legacyRenderSubtreeIntoContainer(
parentComponent: ?React$Component<any, any>,
children: ReactNodeList,
container: DOMContainer,
// dom ,
forceHydrate: boolean,
callback: ?Function,
) {
// ... fiber
// render root
if (!root) {
// ... fiber
if (typeof callback === 'function') {
const originalCallback = callback;
callback = function() {
const instance = getPublicRootInstance(fiberRoot);
originalCallback.call(instance);
};
}
// Initial mount should not be batched.
unbatchedUpdates(() => {
updateContainer(children, fiberRoot, parentComponent, callback);
});
} else {
fiberRoot = root._internalRoot;
// callback
if (typeof callback === 'function') {
const originalCallback = callback;
callback = function() {
const instance = getPublicRootInstance(fiberRoot);
originalCallback.call(instance);
};
}
// Update
updateContainer(children, fiberRoot, parentComponent, callback);
}
return getPublicRootInstance(fiberRoot);
}
updateContainer
の関数においてcallback
は、入ってきた第3のパラメータであり、render
によれば、コンポーネントがレンダリングされたり更新されたりした後に実行され、また、矢印以外の関数の場合には、callback
陰影のコンポーネントを指す.まず、この入参の使い方を振り返ってみます.const instance = render(
<Hello text="123" />,
document.getElementById("page"),
function () { console.log(this) }
);
console.log(instance);
/*
this === instance === Hello
Hello {
isMounted: (...)
replaceState: (...)
props: {text: "123"}
context: {}
refs: {}
updater: {isMounted: ƒ, enqueueSetState: ƒ, enqueueReplaceState: ƒ, enqueueForceUpdate: ƒ}
_reactInternalFiber: FiberNode {tag: 1, key: null, stateNode: Hello, elementType: ƒ, type: ƒ, …}
_reactInternalInstance: {_processChildContext: ƒ}
state: null
__proto__: Component
}
*/
上記のコードにreact
関数を使用したとき、render
の第3のエントリとして匿名関数が入ってきました.render
を印刷した後、render
関数の戻り値をthis
変数に与えてプリントしました.出力されたのはオブジェクト情報であり、render
を使ってinstance
などのフレームをテストしたことがあるので、このreact
に詳しいはずです.これはreact-test-renderer
によって開始される完全なコンポーネント情報を示している.今ソースに戻ります.
instance
を呼び出したときに、fiberRoot
のthisとrender
が返したコンポーネントcallback
の情報は全部render
によって作成されたものです.instance
は、私たちが入ってきたgetPublicRootInstance
を変数react
に与え、新しいcallback
を宣言し、originalCallback
がcallback
を作成し、callback
にinstance
のthisを指し示してcall
のoriginalCallback
のupdateContainer
パラメータに入力した.callback
をどうやって作成するかについては、詳細コードと主流コースは大きくないので、ここでスキップします.この関数がgetPublicRootInstance
に従って情報オブジェクトを提供していることを知っていれば良い.次に、Instance
関数が初めて使用されたかどうかにかかわらず、fiberRoot
はInstance
方法を呼び出し、違いはrender
を初めて使用した場合、legacyRenderSubtreeIntoContainer
はupdateContainer
方法のコールバックで使用された.render
がしたことは、実際にはupdateContainer
が初めて起動したときに、unbatchedUpdates
を大量に更新する必要はなく、この関数がしたことはいくつかのフラグを変更しただけで、すぐにunbatchedUpdates
を更新しました.この部分の論理を省略して、render
の関連部分を重点的に見てみます.function updateContainer(
element: ReactNodeList,
container: OpaqueRoot, // root
parentComponent: ?React$Component<any, any>, // null
callback: ?Function,
): ExpirationTime {
// fiberRoot current fiberRoot fiber
const current = container.current;
// js
const currentTime = requestCurrentTime();
// update
const suspenseConfig = requestCurrentSuspenseConfig();
// @todo , expirationTime
const expirationTime = computeExpirationForFiber(
currentTime,
current,
suspenseConfig,
);
return updateContainerAtExpirationTime(
element, // element
container, // root
parentComponent, // null
expirationTime,
suspenseConfig,
callback,
);
}
関数では、全体のコード論理が非常に明確に見えます.まずupdateContainer
上のCallbackQueue
オブジェクト、すなわちupdateContainer
上のupdateContainer
オブジェクトを取得する.container
に従ってcurrent
を取得し、rootFiber
が識別するための構成を計算し、Fiber
を計算し、requestCurrentTime
を用いてcurrentTime
を更新する.実は、私達はあまり詳細を追究しなくてもいいです.suspenseConfig
とcomputeExpirationForFiber
は何ですか?私たちは簡単にcomputeExpirationForFiber
とcontainer
を見に来ました.ここでは変数に関する内容を抜粋してまとめます.// in react-reconciler/src/ReactFiberExpirationTime.js
export const NoWork = 0;
const NoContext = /* */ 0b000000;
const BatchedContext = /* */ 0b000001;
const EventContext = /* */ 0b000010;
const DiscreteEventContext = /* */ 0b000100;
const LegacyUnbatchedContext = /* */ 0b001000;
const RenderContext = /* */ 0b010000;
const CommitContext = /* */ 0b100000;
// Describes where we are in the React execution stack
let executionContext: number = NoContext;
let currentEventTime: number = NoWork;
//
function requestCurrentTime() {
if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {
// We're inside React, so it's fine to read the actual time.
return msToExpirationTime(now());
}
// We're not inside React, so we may be in the middle of a browser event.
if (currentEventTime !== NoWork) {
// Use the same start time for all updates until we enter React again.
return currentEventTime;
}
// This is the first update since React yielded. Compute a new start time.
currentEventTime = msToExpirationTime(now());
return currentEventTime;
}
実はすることは簡単で、現在の標識のいくつかの状態によって、対応する時間に戻ります.ここではビット操作に関連して状態を識別する設計パターンがあります.具体的な原理が分かりません.の3つの
currentTime
分岐状況は、それぞれReactスケジュールにおいて、ブラウザイベントスケジュールにおいて、および最初の更新で対応する.この中のnow方法はsuspenseConfig
に相当すると理解できる.ブラウザ以外のイベントの場合は、現在のタイムスタンプからExpirationTime
を計算して返します.requestCurrentSuspenseConfig
具体的に何をしましたか?requestCurrentSuspenseConfig
は何ですか?関子を売って、次の専門話requestCurrentTime
章まで一緒に見に来ます.私たちは
requestCurrentTime
のコードを見に行きます.// shared/ReactSharedInternals.js
import React from 'react';
const ReactSharedInternals =
React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
// Prevent newer renderers from RTE when used with older react package versions.
// Current owner and dispatcher used to share the same ref,
// but PR #14548 split them out to better support the react-debug-tools package.
if (!ReactSharedInternals.hasOwnProperty('ReactCurrentDispatcher')) {
ReactSharedInternals.ReactCurrentDispatcher = {
current: null,
};
}
if (!ReactSharedInternals.hasOwnProperty('ReactCurrentBatchConfig')) {
ReactSharedInternals.ReactCurrentBatchConfig = {
suspense: null,
};
}
export default ReactSharedInternals;
// react-reconciler/src/ReactFiberSuspenseConfig.js
const {ReactCurrentBatchConfig} = ReactSharedInternals;
export function requestCurrentSuspenseConfig(): null | SuspenseConfig {
return ReactCurrentBatchConfig.suspense;
}
SuspenseはReactが新しく加入した特性で、いくつかの操作が完了したら、またレンダリングすることができます.if
は、後続の動作のために対応する識別子を設定することである.関数
Date.now()
の論理的に残っている2つの関数currentEventTime
とmsToExpirationTime
を見続けます.expirationTime
コードは、実はすべて識別子に基づいて計算されているexpirationTime
であり、ここで関連するいくつかの異なる計算requestCurrentSuspenseConfig
の方法は、次の章の専門用語requestCurrentSuspenseConfig
の部分にまとめて置いて説明している.export function computeExpirationForFiber(
currentTime: ExpirationTime,
fiber: Fiber,
suspenseConfig: null | SuspenseConfig,
): ExpirationTime {
const mode = fiber.mode;
if ((mode & BatchedMode) === NoMode) {
return Sync;
}
/*
ImmediatePriority 99
UserBlockingPriority 98
NormalPriority 97
LowPriority 96
IdlePriority 95
*/
const priorityLevel = getCurrentPriorityLevel();
if ((mode & ConcurrentMode) === NoMode) {
return priorityLevel === ImmediatePriority ? Sync : Batched;
}
if ((executionContext & RenderContext) !== NoContext) {
// Use whatever time we're already rendering
return renderExpirationTime;
}
let expirationTime;
if (suspenseConfig !== null) {
// Compute an expiration time based on the Suspense timeout.
expirationTime = computeSuspenseExpiration(
currentTime,
suspenseConfig.timeoutMs | 0 || LOW_PRIORITY_EXPIRATION,
);
} else {
// Compute an expiration time based on the Scheduler priority.
switch (priorityLevel) {
case ImmediatePriority:
expirationTime = Sync;
break;
case UserBlockingPriority:
// TODO: Rename this to computeUserBlockingExpiration
expirationTime = computeInteractiveExpiration(currentTime);
break;
case NormalPriority:
case LowPriority: // TODO: Handle LowPriority
// TODO: Rename this to... something better.
expirationTime = computeAsyncExpiration(currentTime);
break;
case IdlePriority:
expirationTime = Never;
break;
default:
invariant(false, 'Expected a valid priority level');
}
}
// If we're in the middle of rendering a tree, do not update at the same
// expiration time that is already rendering.
// TODO: We shouldn't have to do this if the update is on a different root.
// Refactor computeExpirationForFiber + scheduleUpdate so we have access to
// the root when we check for this condition.
if (workInProgressRoot !== null && expirationTime === renderExpirationTime) {
// This is a trick to move this update into a separate batch
expirationTime -= 1;
}
return expirationTime;
}
updateContainer
を計算した後、computeExpirationForFiber
を使用してupdateContainerAtExpirationTime
を更新した.computeExpirationForFiber
の部分を呼び出します.export function updateContainerAtExpirationTime(
element: ReactNodeList,
container: OpaqueRoot,
parentComponent: ?React$Component<any, any>,
expirationTime: ExpirationTime,
suspenseConfig: null | SuspenseConfig,
callback: ?Function,
) {
// TODO: If this is a nested container, this won't be the root.
const current = container.current;
// context ...
return scheduleRootUpdate(
current,
element,
expirationTime,
suspenseConfig,
callback,
);
}
function scheduleRootUpdate(
current: Fiber, // Fiber
element: ReactNodeList,
expirationTime: ExpirationTime,
suspenseConfig: null | SuspenseConfig,
callback: ?Function,
) {
// @todo , update
const update = createUpdate(expirationTime, suspenseConfig);
// Caution: React DevTools currently depends on this property
// being called "element".
update.payload = {element};
callback = callback === undefined ? null : callback;
if (callback !== null) {
update.callback = callback;
}
// enqueue
enqueueUpdate(current, update);
//
scheduleWork(current, expirationTime);
return expirationTime;
}
ExpirationTime
関数のexpirationTime
及びexpirationTime
に関する論理を省略した後、残りは直接ExpirationTime
を呼び出して、rootの更新をスケジュールする.ExpirationTime
において、まずcontainer
オブジェクトが作成され、updateContainerAtExpirationTime
にこのオブジェクトのパスロード属性が付与される.次に、calbackに対して処理を行い、もしcalbackが存在するなら、updateContainerAtExpirationTime
オブジェクトに値を与えます.enqueueUpdateを使用してcontext
オブジェクトを作成します.最後にタスクスケジュールを開始します.この章のコードはDEV
を見ています.どのようにスケジュール作業を行うかはこれまでです.スケジュールフローは非常に大きなワークフローで、後にいくつかの章を分割してこの部分のコードを言います.scheduleRootUpdate
が現在の識別されているscheduleRootUpdate
オブジェクトを更新してdom上にスケジュールするプロセスであるということを知るだけでいいです.次にこのupdate
オブジェクトは何ですか?function createUpdate(
expirationTime: ExpirationTime,
suspenseConfig: null | SuspenseConfig,
): Update<*> {
return {
expirationTime,
suspenseConfig,
/*
tag
export const UpdateState = 0;
export const ReplaceState = 1;
export const ForceUpdate = 2;
//
export const CaptureUpdate = 3;
*/
tag: UpdateState,
// , element(render ),setState
payload: null,
callback: null,
//
next: null,
nextEffect: null,
};
}
オブジェクトは、実はelement
オブジェクトと同じで、識別子付きのオブジェクトです.これは、update
が現在更新する必要があるコンテンツを識別するために使用されるものである.update
がいくつかありますが、scheduleWork
は次にどれぐらいの内容を更新する必要があるかを示しています.scheduleWork
の関数呼び出し、またはupdate
が呼び出したときには、update
を作成してオブジェクトを更新します.update
オブジェクトは互いにnextを介して接続され、一方向チェーンテーブルのデータ構造を形成する.fiber
を了解しました.react
が何をしたかもう一度見てみます.実は
update
をreact
に入れるプロセスです.render
は、記録setState
を保存するための列である.関数の順序を調整して、update
を一番前に置いて、まず作成したupdate
の内容を見てみます.function createUpdateQueue<State>(baseState: State): UpdateQueue<State> {
const queue: UpdateQueue<State> = {
// state
baseState,
//
firstUpdate: null,
lastUpdate: null,
// update
firstCapturedUpdate: null,
lastCapturedUpdate: null,
firstEffect: null,
lastEffect: null,
firstCapturedEffect: null,
lastCapturedEffect: null,
};
return queue;
}
もまた、更新が必要なすべてのupdate
を維持している識別子のオブジェクトである.enqueueUpdate
およびenqueueUpdate
は、それぞれ第1および最後のupdate
を指す.updateQueue
およびupdateQueue
は、キャプチャされたエラー更新を指すupdate
であり、新たに増加したcreateUpdateQueue
のためにサービスされる.updateQueue
は何ですか?分かりました.UpdateQueue
は何をしましたか?function enqueueUpdate<State>(fiber: Fiber, update: Update<State>) {
// Update queues are created lazily.
// current(fiber ) workInProcess
const alternate = fiber.alternate;
let queue1;
let queue2;
// react dom render
if (alternate === null) {
// There's only one fiber.
queue1 = fiber.updateQueue;
queue2 = null;
if (queue1 === null) {
queue1 = fiber.updateQueue = createUpdateQueue(fiber.memoizedState);
}
} else {
// There are two owners.
queue1 = fiber.updateQueue;
queue2 = alternate.updateQueue;
if (queue1 === null) {
if (queue2 === null) {
// Neither fiber has an update queue. Create new ones.
queue1 = fiber.updateQueue = createUpdateQueue(fiber.memoizedState);
queue2 = alternate.updateQueue = createUpdateQueue(
alternate.memoizedState,
);
} else {
// Only one fiber has an update queue. Clone to create a new one.
queue1 = fiber.updateQueue = cloneUpdateQueue(queue2);
}
} else {
if (queue2 === null) {
// Only one fiber has an update queue. Clone to create a new one.
queue2 = alternate.updateQueue = cloneUpdateQueue(queue1);
} else {
// Both owners have an update queue.
}
}
}
// queue2 null
if (queue2 === null || queue1 === queue2) {
// There's only a single queue.
appendUpdateToQueue(queue1, update);
} else {
// There are two queues. We need to append the update to both queues,
// while accounting for the persistent structure of the list — we don't
// want the same update to be added multiple times.
if (queue1.lastUpdate === null || queue2.lastUpdate === null) {
// One of the queues is not empty. We must add the update to both queues.
appendUpdateToQueue(queue1, update);
appendUpdateToQueue(queue2, update);
} else {
// Both queues are non-empty. The last update is the same in both lists,
// because of structural sharing. So, only append to one of the lists.
appendUpdateToQueue(queue1, update);
// But we still need to update the `lastUpdate` pointer of queue2.
queue2.lastUpdate = 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;
}
}
// baseState/firstUpdate/lastUpdate null
function cloneUpdateQueue<State>(
currentQueue: UpdateQueue<State>,
): UpdateQueue<State> {
const queue: UpdateQueue<State> = {
baseState: currentQueue.baseState,
firstUpdate: currentQueue.firstUpdate,
lastUpdate: currentQueue.lastUpdate,
// TODO: With resuming, if we bail out and resuse the child tree, we should
// keep these effects.
firstCapturedUpdate: null,
lastCapturedUpdate: null,
firstEffect: null,
lastEffect: null,
firstCapturedEffect: null,
lastCapturedEffect: null,
};
return queue;
}
update
の場合によってfirstUpdate
とlastUpdate
とが得られ、update
のfirstCapturedUpdate
とlastCapturedUpdate
に追加された.update
は、componentDidCatch
関係を識別するために用いられ、UpdateQueue
部分を後述するときに言及される.この章は
enqueueUpdate
部分の論理を述べ、enqueueUpdate
の概念に言及した.これはfiber.alternate
がタスクスケジュールを行う根拠であり、異なるスケジューリングレベルによって異なるqueue1
を得る.queue2
およびUpdateQueue
オブジェクトに従って、firstUpdate
オブジェクトおよびlastUpdate
オブジェクトを維持するalternate
キューが作成され、current(fiber ) workInProcess
アプリケーション更新の根拠および基礎である.完了したらscheduleWork
関数を使ってスケジュール作業ができます.この章で掘り起こした二つの大きな穴の一つは
updateContainer
で、いったい何を表しているのか、どのように計算されているのか、次の章で説明します.ExpirationTime
を説明し終わったら、react
の内容を持ってきます.ExpirationTime
のコード論理とExpirationTime
コード論理構造はとても近いです.完成したら、Fiber
に入ります.update
全体のスケジュールの章節のソースコードは読みます.