@@ -17,6 +17,7 @@ import type {Fiber} from './ReactFiber';
17
17
import type { ExpirationTime } from './ReactFiberExpirationTime' ;
18
18
import type { HookEffectTag } from './ReactHookEffectTags' ;
19
19
import type { SuspenseConfig } from './ReactFiberSuspenseConfig' ;
20
+ import type { TransitionInstance } from './ReactFiberTransition' ;
20
21
import type { ReactPriorityLevel } from './SchedulerWithReactIntegration' ;
21
22
22
23
import ReactSharedInternals from 'shared/ReactSharedInternals' ;
@@ -53,11 +54,11 @@ import is from 'shared/objectIs';
53
54
import { markWorkInProgressReceivedUpdate } from './ReactFiberBeginWork' ;
54
55
import { requestCurrentSuspenseConfig } from './ReactFiberSuspenseConfig' ;
55
56
import {
56
- UserBlockingPriority ,
57
- NormalPriority ,
58
- runWithPriority ,
59
- getCurrentPriorityLevel ,
60
- } from './SchedulerWithReactIntegration' ;
57
+ startTransition ,
58
+ requestCurrentTransition ,
59
+ cancelPendingTransition ,
60
+ } from './ReactFiberTransition' ;
61
+ import { getCurrentPriorityLevel } from './SchedulerWithReactIntegration' ;
61
62
62
63
const { ReactCurrentDispatcher, ReactCurrentBatchConfig} = ReactSharedInternals ;
63
64
@@ -117,14 +118,11 @@ type Update<S, A> = {
117
118
type UpdateQueue < S , A > = {
118
119
last : Update < S , A> | null ,
119
120
dispatch : ( A => mixed ) | null ,
121
+ pendingTransition : TransitionInstance | null ,
120
122
lastRenderedReducer : ( ( S , A ) => S ) | null ,
121
123
lastRenderedState : S | null ,
122
124
} ;
123
125
124
- type TransitionInstance = { |
125
- pendingExpirationTime : ExpirationTime ,
126
- | } ;
127
-
128
126
export type HookType =
129
127
| 'useState'
130
128
| 'useReducer'
@@ -667,6 +665,7 @@ function mountReducer<S, I, A>(
667
665
const queue = (hook.queue = {
668
666
last: null,
669
667
dispatch: null,
668
+ pendingTransition: null,
670
669
lastRenderedReducer: reducer,
671
670
lastRenderedState: (initialState: any),
672
671
});
@@ -839,6 +838,7 @@ function mountState<S>(
839
838
const queue = (hook.queue = {
840
839
last: null,
841
840
dispatch: null,
841
+ pendingTransition: null,
842
842
lastRenderedReducer: basicStateReducer,
843
843
lastRenderedState: (initialState: any),
844
844
});
@@ -1176,89 +1176,14 @@ function updateDeferredValue<T>(
1176
1176
return prevValue;
1177
1177
}
1178
1178
1179
- function startTransition(fiber, instance, config, callback) {
1180
- let resolvedConfig: SuspenseConfig | null =
1181
- config === undefined ? null : config;
1182
-
1183
- // TODO: runWithPriority shouldn't be necessary here. React should manage its
1184
- // own concept of priority, and only consult Scheduler for updates that are
1185
- // scheduled from outside a React context.
1186
- const priorityLevel = getCurrentPriorityLevel();
1187
- runWithPriority(
1188
- priorityLevel < UserBlockingPriority ? UserBlockingPriority : priorityLevel,
1189
- () => {
1190
- const currentTime = requestCurrentTimeForUpdate();
1191
- const expirationTime = computeExpirationForFiber(
1192
- currentTime,
1193
- fiber,
1194
- null,
1195
- );
1196
- scheduleWork(fiber, expirationTime);
1197
- },
1198
- );
1199
- runWithPriority(
1200
- priorityLevel > NormalPriority ? NormalPriority : priorityLevel,
1201
- () => {
1202
- const currentTime = requestCurrentTimeForUpdate();
1203
- let expirationTime = computeExpirationForFiber(
1204
- currentTime,
1205
- fiber,
1206
- resolvedConfig,
1207
- );
1208
- // Set the expiration time at which the pending transition will finish.
1209
- // Because there's only a single transition per useTransition hook, we
1210
- // don't need a queue here; we can cheat by only tracking the most
1211
- // recently scheduled transition.
1212
- // TODO: This trick depends on transition expiration times being
1213
- // monotonically decreasing in priority, but since expiration times
1214
- // currently correspond to ` timeoutMs `, that might not be true if
1215
- // ` timeoutMs ` changes to something smaller before the previous transition
1216
- // resolves. But this is a temporary edge case, since we're about to
1217
- // remove the correspondence between ` timeoutMs ` and the expiration time.
1218
- const oldPendingExpirationTime = instance.pendingExpirationTime;
1219
- while (
1220
- oldPendingExpirationTime !== NoWork &&
1221
- oldPendingExpirationTime <= expirationTime
1222
- ) {
1223
- // Temporary hack to make pendingExpirationTime monotonically decreasing
1224
- if (resolvedConfig === null) {
1225
- resolvedConfig = {
1226
- timeoutMs: 5250,
1227
- };
1228
- } else {
1229
- resolvedConfig = {
1230
- timeoutMs: (resolvedConfig.timeoutMs | 0 || 5000) + 250,
1231
- busyDelayMs: resolvedConfig.busyDelayMs,
1232
- busyMinDurationMs: resolvedConfig.busyMinDurationMs,
1233
- };
1234
- }
1235
- expirationTime = computeExpirationForFiber(
1236
- currentTime,
1237
- fiber,
1238
- resolvedConfig,
1239
- );
1240
- }
1241
- instance.pendingExpirationTime = expirationTime;
1242
-
1243
- scheduleWork(fiber, expirationTime);
1244
- const previousConfig = ReactCurrentBatchConfig.suspense;
1245
- ReactCurrentBatchConfig.suspense = resolvedConfig;
1246
- try {
1247
- callback();
1248
- } finally {
1249
- ReactCurrentBatchConfig.suspense = previousConfig;
1250
- }
1251
- },
1252
- );
1253
- }
1254
-
1255
1179
function mountTransition(
1256
1180
config: SuspenseConfig | void | null,
1257
1181
): [(() => void) => void, boolean] {
1258
1182
const hook = mountWorkInProgressHook();
1259
-
1183
+ const fiber = ((currentlyRenderingFiber: any): Fiber);
1260
1184
const instance: TransitionInstance = {
1261
1185
pendingExpirationTime: NoWork,
1186
+ fiber,
1262
1187
};
1263
1188
// TODO: Intentionally storing this on the queue field to avoid adding a new/
1264
1189
// one; ` queue ` should be a union.
@@ -1270,15 +1195,9 @@ function mountTransition(
1270
1195
// Then we don't have to recompute the callback whenever it changes. However,
1271
1196
// if we don't end up changing the API, we should at least optimize this
1272
1197
// to use the same hook instead of a separate hook just for the callback.
1273
- const start = mountCallback (
1274
- startTransition . bind (
1275
- null ,
1276
- ( ( currentlyRenderingFiber : any ) : Fiber ) ,
1277
- instance ,
1278
- config ,
1279
- ) ,
1280
- [ config ] ,
1281
- ) ;
1198
+ const start = mountCallback(startTransition.bind(null, instance, config), [
1199
+ config,
1200
+ ]);
1282
1201
1283
1202
const resolvedExpirationTime = NoWork;
1284
1203
hook.memoizedState = {
@@ -1374,15 +1293,9 @@ function updateTransition(
1374
1293
resolvedExpirationTime: newResolvedExpirationTime,
1375
1294
};
1376
1295
1377
- const start = updateCallback (
1378
- startTransition . bind (
1379
- null ,
1380
- ( ( currentlyRenderingFiber : any ) : Fiber ) ,
1381
- instance ,
1382
- config ,
1383
- ) ,
1384
- [ config ] ,
1385
- ) ;
1296
+ const start = updateCallback(startTransition.bind(null, instance, config), [
1297
+ config,
1298
+ ]);
1386
1299
1387
1300
return [start, newIsPending];
1388
1301
}
@@ -1444,6 +1357,7 @@ function dispatchAction<S, A>(
1444
1357
} else {
1445
1358
const currentTime = requestCurrentTimeForUpdate();
1446
1359
const suspenseConfig = requestCurrentSuspenseConfig();
1360
+ const transition = requestCurrentTransition();
1447
1361
const expirationTime = computeExpirationForFiber(
1448
1362
currentTime,
1449
1363
fiber,
@@ -1524,6 +1438,20 @@ function dispatchAction<S, A>(
1524
1438
warnIfNotCurrentlyActingUpdatesInDev(fiber);
1525
1439
}
1526
1440
}
1441
+
1442
+ if (transition !== null) {
1443
+ const prevPendingTransition = queue.pendingTransition;
1444
+ if (transition !== prevPendingTransition) {
1445
+ queue.pendingTransition = transition;
1446
+ if (prevPendingTransition !== null) {
1447
+ // There's already a pending transition on this queue. The new
1448
+ // transition supersedes the old one. Turn of the ` isPending ` state
1449
+ // of the previous transition.
1450
+ cancelPendingTransition ( prevPendingTransition ) ;
1451
+ }
1452
+ }
1453
+ }
1454
+
1527
1455
scheduleWork ( fiber , expirationTime ) ;
1528
1456
}
1529
1457
}
0 commit comments