@@ -16,6 +16,7 @@ import type {Fiber} from './ReactFiber';
16
16
import type { ExpirationTime } from './ReactFiberExpirationTime' ;
17
17
import type { HookEffectTag } from './ReactHookEffectTags' ;
18
18
import type { SuspenseConfig } from './ReactFiberSuspenseConfig' ;
19
+ import type { TransitionInstance } from './ReactFiberTransition' ;
19
20
import type { ReactPriorityLevel } from './SchedulerWithReactIntegration' ;
20
21
21
22
import ReactSharedInternals from 'shared/ReactSharedInternals' ;
@@ -52,11 +53,11 @@ import is from 'shared/objectIs';
52
53
import { markWorkInProgressReceivedUpdate } from './ReactFiberBeginWork' ;
53
54
import { requestCurrentSuspenseConfig } from './ReactFiberSuspenseConfig' ;
54
55
import {
55
- UserBlockingPriority ,
56
- NormalPriority ,
57
- runWithPriority ,
58
- getCurrentPriorityLevel ,
59
- } from './SchedulerWithReactIntegration' ;
56
+ startTransition ,
57
+ requestCurrentTransition ,
58
+ cancelPendingTransition ,
59
+ } from './ReactFiberTransition' ;
60
+ import { getCurrentPriorityLevel } from './SchedulerWithReactIntegration' ;
60
61
61
62
const { ReactCurrentDispatcher, ReactCurrentBatchConfig} = ReactSharedInternals ;
62
63
@@ -116,14 +117,11 @@ type Update<S, A> = {
116
117
type UpdateQueue < S , A > = {
117
118
last : Update < S , A> | null ,
118
119
dispatch : ( A => mixed ) | null ,
120
+ pendingTransition : TransitionInstance | null ,
119
121
lastRenderedReducer : ( ( S , A ) => S ) | null ,
120
122
lastRenderedState : S | null ,
121
123
} ;
122
124
123
- type TransitionInstance = { |
124
- pendingExpirationTime : ExpirationTime ,
125
- | } ;
126
-
127
125
export type HookType =
128
126
| 'useState'
129
127
| 'useReducer'
@@ -651,6 +649,7 @@ function mountReducer<S, I, A>(
651
649
const queue = (hook.queue = {
652
650
last: null,
653
651
dispatch: null,
652
+ pendingTransition: null,
654
653
lastRenderedReducer: reducer,
655
654
lastRenderedState: (initialState: any),
656
655
});
@@ -822,6 +821,7 @@ function mountState<S>(
822
821
const queue = (hook.queue = {
823
822
last: null,
824
823
dispatch: null,
824
+ pendingTransition: null,
825
825
lastRenderedReducer: basicStateReducer,
826
826
lastRenderedState: (initialState: any),
827
827
});
@@ -1157,89 +1157,14 @@ function updateDeferredValue<T>(
1157
1157
return prevValue;
1158
1158
}
1159
1159
1160
- function startTransition(fiber, instance, config, callback) {
1161
- let resolvedConfig: SuspenseConfig | null =
1162
- config === undefined ? null : config;
1163
-
1164
- // TODO: runWithPriority shouldn't be necessary here. React should manage its
1165
- // own concept of priority, and only consult Scheduler for updates that are
1166
- // scheduled from outside a React context.
1167
- const priorityLevel = getCurrentPriorityLevel();
1168
- runWithPriority(
1169
- priorityLevel < UserBlockingPriority ? UserBlockingPriority : priorityLevel,
1170
- () => {
1171
- const currentTime = requestCurrentTimeForUpdate();
1172
- const expirationTime = computeExpirationForFiber(
1173
- currentTime,
1174
- fiber,
1175
- null,
1176
- );
1177
- scheduleWork(fiber, expirationTime);
1178
- },
1179
- );
1180
- runWithPriority(
1181
- priorityLevel > NormalPriority ? NormalPriority : priorityLevel,
1182
- () => {
1183
- const currentTime = requestCurrentTimeForUpdate();
1184
- let expirationTime = computeExpirationForFiber(
1185
- currentTime,
1186
- fiber,
1187
- resolvedConfig,
1188
- );
1189
- // Set the expiration time at which the pending transition will finish.
1190
- // Because there's only a single transition per useTransition hook, we
1191
- // don't need a queue here; we can cheat by only tracking the most
1192
- // recently scheduled transition.
1193
- // TODO: This trick depends on transition expiration times being
1194
- // monotonically decreasing in priority, but since expiration times
1195
- // currently correspond to ` timeoutMs `, that might not be true if
1196
- // ` timeoutMs ` changes to something smaller before the previous transition
1197
- // resolves. But this is a temporary edge case, since we're about to
1198
- // remove the correspondence between ` timeoutMs ` and the expiration time.
1199
- const oldPendingExpirationTime = instance.pendingExpirationTime;
1200
- while (
1201
- oldPendingExpirationTime !== NoWork &&
1202
- oldPendingExpirationTime <= expirationTime
1203
- ) {
1204
- // Temporary hack to make pendingExpirationTime monotonically decreasing
1205
- if (resolvedConfig === null) {
1206
- resolvedConfig = {
1207
- timeoutMs: 5250,
1208
- };
1209
- } else {
1210
- resolvedConfig = {
1211
- timeoutMs: (resolvedConfig.timeoutMs | 0 || 5000) + 250,
1212
- busyDelayMs: resolvedConfig.busyDelayMs,
1213
- busyMinDurationMs: resolvedConfig.busyMinDurationMs,
1214
- };
1215
- }
1216
- expirationTime = computeExpirationForFiber(
1217
- currentTime,
1218
- fiber,
1219
- resolvedConfig,
1220
- );
1221
- }
1222
- instance.pendingExpirationTime = expirationTime;
1223
-
1224
- scheduleWork(fiber, expirationTime);
1225
- const previousConfig = ReactCurrentBatchConfig.suspense;
1226
- ReactCurrentBatchConfig.suspense = resolvedConfig;
1227
- try {
1228
- callback();
1229
- } finally {
1230
- ReactCurrentBatchConfig.suspense = previousConfig;
1231
- }
1232
- },
1233
- );
1234
- }
1235
-
1236
1160
function mountTransition(
1237
1161
config: SuspenseConfig | void | null,
1238
1162
): [(() => void) => void, boolean] {
1239
1163
const hook = mountWorkInProgressHook();
1240
-
1164
+ const fiber = ((currentlyRenderingFiber: any): Fiber);
1241
1165
const instance: TransitionInstance = {
1242
1166
pendingExpirationTime: NoWork,
1167
+ fiber,
1243
1168
};
1244
1169
// TODO: Intentionally storing this on the queue field to avoid adding a new/
1245
1170
// one; ` queue ` should be a union.
@@ -1251,15 +1176,9 @@ function mountTransition(
1251
1176
// Then we don't have to recompute the callback whenever it changes. However,
1252
1177
// if we don't end up changing the API, we should at least optimize this
1253
1178
// to use the same hook instead of a separate hook just for the callback.
1254
- const start = mountCallback (
1255
- startTransition . bind (
1256
- null ,
1257
- ( ( currentlyRenderingFiber : any ) : Fiber ) ,
1258
- instance ,
1259
- config ,
1260
- ) ,
1261
- [ config ] ,
1262
- ) ;
1179
+ const start = mountCallback(startTransition.bind(null, instance, config), [
1180
+ config,
1181
+ ]);
1263
1182
1264
1183
const resolvedExpirationTime = NoWork;
1265
1184
hook.memoizedState = {
@@ -1355,15 +1274,9 @@ function updateTransition(
1355
1274
resolvedExpirationTime: newResolvedExpirationTime,
1356
1275
};
1357
1276
1358
- const start = updateCallback (
1359
- startTransition . bind (
1360
- null ,
1361
- ( ( currentlyRenderingFiber : any ) : Fiber ) ,
1362
- instance ,
1363
- config ,
1364
- ) ,
1365
- [ config ] ,
1366
- ) ;
1277
+ const start = updateCallback(startTransition.bind(null, instance, config), [
1278
+ config,
1279
+ ]);
1367
1280
1368
1281
return [start, newIsPending];
1369
1282
}
@@ -1425,6 +1338,7 @@ function dispatchAction<S, A>(
1425
1338
} else {
1426
1339
const currentTime = requestCurrentTimeForUpdate();
1427
1340
const suspenseConfig = requestCurrentSuspenseConfig();
1341
+ const transition = requestCurrentTransition();
1428
1342
const expirationTime = computeExpirationForFiber(
1429
1343
currentTime,
1430
1344
fiber,
@@ -1505,6 +1419,20 @@ function dispatchAction<S, A>(
1505
1419
warnIfNotCurrentlyActingUpdatesInDev(fiber);
1506
1420
}
1507
1421
}
1422
+
1423
+ if (transition !== null) {
1424
+ const prevPendingTransition = queue.pendingTransition;
1425
+ if (transition !== prevPendingTransition) {
1426
+ queue.pendingTransition = transition;
1427
+ if (prevPendingTransition !== null) {
1428
+ // There's already a pending transition on this queue. The new
1429
+ // transition supersedes the old one. Turn of the ` isPending ` state
1430
+ // of the previous transition.
1431
+ cancelPendingTransition ( prevPendingTransition ) ;
1432
+ }
1433
+ }
1434
+ }
1435
+
1508
1436
scheduleWork ( fiber , expirationTime ) ;
1509
1437
}
1510
1438
}
0 commit comments