react act
一、作用
二、动作
备注
ReactSharedInternals()由 ReactSharedInternals 提供queueMacrotask()由 queueMacrotask 实现
export function act<T>(callback: () => T | Thenable<T>): Thenable<T> {
if (__DEV__) {
// When ReactSharedInternals.actQueue is not null, it signals to React that
// we're currently inside an `act` scope. React will push all its tasks to
// this queue instead of scheduling them with platform APIs.
//
// 当 ReactSharedInternals.actQueue 不为 null 时,它会向 React 发出信号,表示我们
// 当前处于 `act` 范围内。React 会将所有任务推送到该队列中,而不是使用平台 API 调度它们。
//
// We set this to an empty array when we first enter an `act` scope, and
// only unset it once we've left the outermost `act` scope — remember that
// `act` calls can be nested.
//
// 当我们第一次进入 `act` 范围时,会将其设置为空数组,并且只有在离开最外层的 `act` 范围
// 后才会取消 — 记住, `act` 调用是可以嵌套的。
//
// If we're already inside an `act` scope, reuse the existing queue.
// 如果我们已经在 `act` 范围内,则重用现有队列。
const prevIsBatchingLegacy = !disableLegacyMode
? ReactSharedInternals.isBatchingLegacy
: false;
const prevActQueue = ReactSharedInternals.actQueue;
const prevActScopeDepth = actScopeDepth;
actScopeDepth++;
const queue = (ReactSharedInternals.actQueue =
prevActQueue !== null ? prevActQueue : []);
// Used to reproduce behavior of `batchedUpdates` in legacy mode. Only
// set to `true` while the given callback is executed, not for updates
// triggered during an async event, because this is how the legacy
// implementation of `act` behaved.
//
// 用于在旧版模式下重现 `batchedUpdates` 的行为。仅在执行给定回调时设置为 `true`,
// 而不会在异步事件触发的更新中设置,因为这是 `act` 的旧版实现的行为方式。
if (!disableLegacyMode) {
ReactSharedInternals.isBatchingLegacy = true;
}
let result;
// This tracks whether the `act` call is awaited. In certain cases, not
// awaiting it is a mistake, so we will detect that and warn.
//
// 这用于跟踪 `act` 调用是否被 await。在某些情况下,不 await 它是一个错误,所以我们会
// 检测并给出警告。
let didAwaitActCall = false;
try {
// Reset this to `false` right before entering the React work loop. The
// only place we ever read this fields is just below, right after running
// the callback. So we don't need to reset after the callback runs.
//
// 在进入 React 工作循环之前,将其重置为 `false`。
// 我们唯一读取这个字段的地方就在下面,在回调运行之后。所以回调执行后我们不需要重置。
if (!disableLegacyMode) {
ReactSharedInternals.didScheduleLegacyUpdate = false;
}
result = callback();
const didScheduleLegacyUpdate = !disableLegacyMode
? ReactSharedInternals.didScheduleLegacyUpdate
: false;
// Replicate behavior of original `act` implementation in legacy mode,
// which flushed updates immediately after the scope function exits, even
// if it's an async function.
// 在旧模式下复制原始 `act` 实现的行为,在作用域函数退出后立即刷新更新,即使它是一个异步函数。
if (!prevIsBatchingLegacy && didScheduleLegacyUpdate) {
flushActQueue(queue);
}
// `isBatchingLegacy` gets reset using the regular stack, not the async
// one used to track `act` scopes. Why, you may be wondering? Because
// that's how it worked before version 18. Yes, it's confusing! We should
// delete legacy mode!!
// `isBatchingLegacy` 是用常规堆栈重置的,而不是用于跟踪 `act` 范围的异步堆栈。你可能
// 会想,为什么?因为这是在 18 版本之前的工作方式。是的,这很让人困惑!我们应该删除遗留模式!!
if (!disableLegacyMode) {
ReactSharedInternals.isBatchingLegacy = prevIsBatchingLegacy;
}
} catch (error) {
// `isBatchingLegacy` gets reset using the regular stack, not the async
// one used to track `act` scopes. Why, you may be wondering? Because
// that's how it worked before version 18. Yes, it's confusing! We should
// delete legacy mode!!
//
// `isBatchingLegacy` 是用常规堆栈重置的,而不是用于跟踪 `act` 范围的异步堆栈。你可能
// 会想,为什么?因为这是在 18 版本之前的工作方式。是的,这很让人困惑!我们应该删除遗留模式!!
ReactSharedInternals.thrownErrors.push(error);
}
if (ReactSharedInternals.thrownErrors.length > 0) {
if (!disableLegacyMode) {
ReactSharedInternals.isBatchingLegacy = prevIsBatchingLegacy;
}
popActScope(prevActQueue, prevActScopeDepth);
const thrownError = aggregateErrors(ReactSharedInternals.thrownErrors);
ReactSharedInternals.thrownErrors.length = 0;
throw thrownError;
}
if (
result !== null &&
typeof result === 'object' &&
typeof result.then === 'function'
) {
// A promise/thenable was returned from the callback. Wait for it to
// resolve before flushing the queue.
// 回调返回了一个 promise/thenable。等待它解决后再清空队列。
//
// If `act` were implemented as an async function, this whole block could
// be a single `await` call. That's really the only difference between
// this branch and the next one.
//
// 如果 `act` 被实现为一个异步函数,那么整个代码块可以用一个单独的 `await` 调用完成。
// 这实际上是这一分支与下一个分支之间的唯一区别。
const thenable = result as any as Thenable<T>;
// Warn if the an `act` call with an async scope is not awaited. In a
// future release, consider making this an error.
//
// 如果对带有异步作用域的 `act` 调用未使用 await,则发出警告。
// 在未来的版本中,考虑将其作为错误处理。
queueSeveralMicrotasks(() => {
if (!didAwaitActCall && !didWarnNoAwaitAct) {
didWarnNoAwaitAct = true;
console.error(
'You called act(async () => ...) without await. ' +
'This could lead to unexpected testing behaviour, ' +
'interleaving multiple act calls and mixing their ' +
'scopes. ' +
'You should - await act(async () => ...);',
);
}
});
return {
then(resolve: (t: T) => mixed, reject: (mixed: mixed) => mixed) {
didAwaitActCall = true;
thenable.then(
returnValue => {
popActScope(prevActQueue, prevActScopeDepth);
if (prevActScopeDepth === 0) {
// We're exiting the outermost `act` scope. Flush the queue.
// 我们正在退出最外层的 `act` 范围。刷新队列。
try {
flushActQueue(queue);
queueMacrotask(() =>
// Recursively flush tasks scheduled by a microtask.
// 递归刷新由微任务调度的任务。
recursivelyFlushAsyncActWork(returnValue, resolve, reject),
);
} catch (error) {
// `thenable` might not be a real promise, and `flushActQueue`
// might throw, so we need to wrap `flushActQueue` in a
// try/catch.
//
// `thenable` 可能不是真正的 promise,而且 `flushActQueue` 可能会抛出
// 错误,所以我们需要用 try/catch 包裹 `flushActQueue`。
ReactSharedInternals.thrownErrors.push(error);
}
if (ReactSharedInternals.thrownErrors.length > 0) {
const thrownError = aggregateErrors(
ReactSharedInternals.thrownErrors,
);
ReactSharedInternals.thrownErrors.length = 0;
reject(thrownError);
}
} else {
resolve(returnValue);
}
},
error => {
popActScope(prevActQueue, prevActScopeDepth);
if (ReactSharedInternals.thrownErrors.length > 0) {
const thrownError = aggregateErrors(
ReactSharedInternals.thrownErrors,
);
ReactSharedInternals.thrownErrors.length = 0;
reject(thrownError);
} else {
reject(error);
}
},
);
},
};
} else {
const returnValue: T = result as any;
// The callback is not an async function. Exit the current
// scope immediately.
// 回调函数不是异步函数。立即退出当前作用域。
popActScope(prevActQueue, prevActScopeDepth);
if (prevActScopeDepth === 0) {
// We're exiting the outermost `act` scope. Flush the queue.
// 我们正在退出最外层的 `act` 范围。刷新队列。
flushActQueue(queue);
// If the queue is not empty, it implies that we intentionally yielded
// to the main thread, because something suspended. We will continue
// in an asynchronous task.
//
// 如果队列不为空,这意味着我们有意将执行权交回主线程,因为有些操作被挂起了。
// 我们将在异步任务中继续执行。
//
// Warn if something suspends but the `act` call is not awaited.
// In a future release, consider making this an error.
//
// 如果有某些操作挂起但 `act` 调用未被 await,则发出警告。
// 在未来的版本中,可以考虑将其视为错误。
if (queue.length !== 0) {
queueSeveralMicrotasks(() => {
if (!didAwaitActCall && !didWarnNoAwaitAct) {
didWarnNoAwaitAct = true;
console.error(
'A component suspended inside an `act` scope, but the ' +
'`act` call was not awaited. When testing React ' +
'components that depend on asynchronous data, you must ' +
'await the result:\n\n' +
'await act(() => ...)',
);
}
});
}
// Like many things in this module, this is next part is confusing.
// 和本模块的许多内容一样,下一部分也很令人困惑。
//
// We do not currently require every `act` call that is passed a
// callback to be awaited, through arguably we should. Since this
// callback was synchronous, we need to exit the current scope before
// returning.
//
// 我们目前不要求对传入回调的每个 `act` 调用都使用 await,尽管从某种程度上来说我们
// 应该这么做。由于这个回调是同步的,我们需要在返回之前退出当前作用域。
//
// However, if thenable we're about to return *is* awaited, we'll
// immediately restore the current scope. So it shouldn't observable.
//
// 但是,如果我们即将返回的 thenable 被 await,我们将立即恢复当前作用域。所以它
// 不应被观察到。
//
// This doesn't affect the case where the scope callback is async,
// because we always require those calls to be awaited.
//
// 这不会影响作用域回调是异步的情况,因为我们总是要求这些调用被 await。
//
// TODO: In a future version, consider always requiring all `act` calls
// to be awaited, regardless of whether the callback is sync or async.
//
// 待办:在未来的版本中,考虑始终要求所有 `act` 调用都要使用 await,无论回调是同步
// 还是异步。
ReactSharedInternals.actQueue = null;
}
if (ReactSharedInternals.thrownErrors.length > 0) {
const thrownError = aggregateErrors(ReactSharedInternals.thrownErrors);
ReactSharedInternals.thrownErrors.length = 0;
throw thrownError;
}
return {
then(resolve: (t: T) => mixed, reject: (mixed) => mixed) {
didAwaitActCall = true;
if (prevActScopeDepth === 0) {
// If the `act` call is awaited, restore the queue we were
// using before (see long comment above) so we can flush it.
// 如果 `act` 调用被等待,恢复我们之前使用的队列(见上面的详细注释),以便我们可以清空它。
ReactSharedInternals.actQueue = queue;
queueMacrotask(() =>
// Recursively flush tasks scheduled by a microtask.
// 递归刷新由微任务调度的任务。
recursivelyFlushAsyncActWork(returnValue, resolve, reject),
);
} else {
resolve(returnValue);
}
},
};
}
} else {
throw new Error('act(...) is not supported in production builds of React.');
}
}
三、常量
// Some of our warnings attempt to detect if the `act` call is awaited by
// checking in an asynchronous task. Wait a few microtasks before checking. The
// only reason one isn't sufficient is we want to accommodate the case where an
// `act` call is returned from an async function without first being awaited,
// since that's a somewhat common pattern. If you do this too many times in a
// nested sequence, you might get a warning, but you can always fix by awaiting
// the call.
//
// 我们的一些警告试图通过在异步任务中检查来检测 `act` 调用是否已被等待。检查之前等待几个微任务。
// 唯一不够的原因是我们希望兼容 `act` 调用从异步函数返回但尚未被等待的情况,因为这是一个相当常见
// 的模式。如果你在嵌套序列中多次这样做,你可能会收到警告,但你总是可以通过等待该调用来解决。
//
// A macrotask would also work (and is the fallback) but depending on the test
// environment it may cause the warning to fire too late.
// 宏任务也可以使用(也是备用方案),但根据测试环境,它可能会导致警告太晚触发。
const queueSeveralMicrotasks =
typeof queueMicrotask === 'function'
? (callback: () => void) => {
queueMicrotask(() => queueMicrotask(callback));
}
: queueMacrotask;
四、变量
1. 行为范围深度
// `act` calls can be nested, so we track the depth. This represents the
// number of `act` scopes on the stack.
// `act` 调用可以嵌套,所以我们跟踪深度。这表示堆栈上 `act` 作用域的数量。
let actScopeDepth = 0;
2. 已警告未使用 await 执行
// We only warn the first time you neglect to await an async `act` scope.
// 我们只在你第一次忽略等待异步 `act` 范围时发出警告。
let didWarnNoAwaitAct = false;
五、工具
1. 汇总错误
function aggregateErrors(errors: Array<mixed>): mixed {
if (errors.length > 1 && typeof AggregateError === 'function') {
return new AggregateError(errors);
}
return errors[0];
}
2. 弹出活动范围
function popActScope(
prevActQueue: null | Array<RendererTask>,
prevActScopeDepth: number,
) {
if (__DEV__) {
if (prevActScopeDepth !== actScopeDepth - 1) {
console.error(
'You seem to have overlapping act() calls, this is not supported. ' +
'Be sure to await previous act() calls before making a new one. ',
);
}
actScopeDepth = prevActScopeDepth;
}
}
3. 递归异步刷新 Act 工作
备注
queueMacrotask()由 queueMacrotask 实现
function recursivelyFlushAsyncActWork<T>(
returnValue: T,
resolve: (t: T) => mixed,
reject: (mixed: mixed) => mixed,
) {
if (__DEV__) {
// Check if any tasks were scheduled asynchronously.
// 检查是否有任何任务是异步调度的。
const queue = ReactSharedInternals.actQueue;
if (queue !== null) {
if (queue.length !== 0) {
// Async tasks were scheduled, mostly likely in a microtask.
// Keep flushing until there are no more.
// 异步任务已被调度,很可能是在微任务中。不断刷新,直到没有更多任务为止。
try {
flushActQueue(queue);
// The work we just performed may have schedule additional async
// tasks. Wait a macrotask and check again.
// 我们刚刚执行的工作可能已经安排了额外的异步任务。 等待一个宏任务后再检查一次。
queueMacrotask(() =>
recursivelyFlushAsyncActWork(returnValue, resolve, reject),
);
return;
} catch (error) {
// Leave remaining tasks on the queue if something throws.
// 如果出现异常,将剩余任务留在队列中。
ReactSharedInternals.thrownErrors.push(error);
}
} else {
// The queue is empty. We can finish.
// 队列为空。我们可以结束。
ReactSharedInternals.actQueue = null;
}
}
if (ReactSharedInternals.thrownErrors.length > 0) {
const thrownError = aggregateErrors(ReactSharedInternals.thrownErrors);
ReactSharedInternals.thrownErrors.length = 0;
reject(thrownError);
} else {
resolve(returnValue);
}
}
}
4. 刷新活动队列
备注
ReactSharedInternals()由 ReactSharedInternals 实现()由 [] 提供()由 渲染平台提供
let isFlushing = false;
function flushActQueue(queue: Array<RendererTask>) {
if (__DEV__) {
if (!isFlushing) {
// Prevent re-entrance.
// 防止重入。
isFlushing = true;
let i = 0;
try {
for (; i < queue.length; i++) {
let callback: RendererTask = queue[i];
do {
ReactSharedInternals.didUsePromise = false;
const continuation = callback(false);
if (continuation !== null) {
if (ReactSharedInternals.didUsePromise) {
// The component just suspended. Yield to the main thread in
// case the promise is already resolved. If so, it will ping in
// a microtask and we can resume without unwinding the stack.
// 组件刚刚挂起。如果 promise 已经解决,就让出给主线程。
// 如果是这样,它会在微任务中发送信号,我们可以在不展开调用栈的情况下恢复。
queue[i] = callback;
queue.splice(0, i);
return;
}
callback = continuation;
} else {
break;
}
} while (true);
}
// We flushed the entire queue.
// 我们清空了整个队列。
queue.length = 0;
} catch (error) {
// If something throws, leave the remaining callbacks on the queue.
// 如果发生异常,保留队列中剩余的回调。
queue.splice(0, i + 1);
ReactSharedInternals.thrownErrors.push(error);
} finally {
isFlushing = false;
}
}
}
}