mirror of
https://github.com/facebook/react.git
synced 2026-02-24 04:33:04 +00:00
* [useSyncExternalStore] Remove extra hook object Because we already track `getSnapshot` and `value` on the store instance, we don't need to also track them as effect dependencies. And because the effect doesn't require any clean-up, we don't need to track a `destroy` function. So, we don't need to store any additional state for this effect. We can call `pushEffect` directly, and only during renders where something has changed. This saves some memory, but my main motivation is because I plan to use this same logic to schedule a pre-commit consistency check. (See the inline comments for more details.) * Split shouldTimeSlice into two separate functions Lanes that are blocking (SyncLane, and DefaultLane inside a blocking- by-default root) are always blocking for a given root. Whereas expired lanes can expire while the render phase is already in progress. I want to check if a lane is blocking without checking whether it expired, so I split `shouldTimeSlice` into two separate functions. I'll use this in the next step. * Check for store mutations before commit When a store is read for the first time, or when `subscribe` or `getSnapshot` changes, during a concurrent render, we have to check at the end of the render phase whether the store was mutated by an concurrent event. In the userspace shim, we perform this check in a layout effect, and patch up any inconsistencies by scheduling another render + commit. However, even though we patch them up in the next render, the parent layout effects that fire in the original render will still observe an inconsistent tree. In the native implementation, we can instead check for inconsistencies right after the root is completed, before entering the commit phase. If we do detect a mutaiton, we can discard the tree and re-render before firing any effects. The re-render is synchronous to block further concurrent mutations (which is also what we do to recover from tearing bugs that result in an error). After the synchronous re-render, we can assume the tree the tree is consistent and continue with the normal algorithm for finishing a completed root (i.e. either suspend or commit). The result is that layout effects will always observe a consistent tree.
115 lines
3.4 KiB
JavaScript
115 lines
3.4 KiB
JavaScript
'use strict';
|
|
|
|
// These flags can be in a @gate pragma to declare that a test depends on
|
|
// certain conditions. They're like GKs.
|
|
//
|
|
// Examples:
|
|
// // @gate enableSomeAPI
|
|
// test('uses an unstable API', () => {/*...*/})
|
|
//
|
|
// // @gate __DEV__
|
|
// test('only passes in development', () => {/*...*/})
|
|
//
|
|
// Most flags are defined in ReactFeatureFlags. If it's defined there, you don't
|
|
// have to do anything extra here.
|
|
//
|
|
// There are also flags based on the environment, like __DEV__. Feel free to
|
|
// add new flags and aliases below.
|
|
//
|
|
// You can also combine flags using multiple gates:
|
|
//
|
|
// // @gate enableSomeAPI
|
|
// // @gate __DEV__
|
|
// test('both conditions must pass', () => {/*...*/})
|
|
//
|
|
// Or using logical operators
|
|
// // @gate enableSomeAPI && __DEV__
|
|
// test('both conditions must pass', () => {/*...*/})
|
|
//
|
|
// Negation also works:
|
|
// // @gate !deprecateLegacyContext
|
|
// test('uses a deprecated feature', () => {/*...*/})
|
|
|
|
// These flags are based on the environment and don't change for the entire
|
|
// test run.
|
|
const environmentFlags = {
|
|
__DEV__,
|
|
build: __DEV__ ? 'development' : 'production',
|
|
|
|
// TODO: Should "experimental" also imply "modern"? Maybe we should
|
|
// always compare to the channel?
|
|
experimental: __EXPERIMENTAL__,
|
|
// Similarly, should stable imply "classic"?
|
|
stable: !__EXPERIMENTAL__,
|
|
|
|
variant: __VARIANT__,
|
|
|
|
persistent: global.__PERSISTENT__ === true,
|
|
|
|
// Use this for tests that are known to be broken.
|
|
FIXME: false,
|
|
|
|
// Turn these flags back on (or delete) once the effect list is removed in
|
|
// favor of a depth-first traversal using `subtreeTags`.
|
|
dfsEffectsRefactor: true,
|
|
enableUseJSStackToTrackPassiveDurations: false,
|
|
};
|
|
|
|
function getTestFlags() {
|
|
// These are required on demand because some of our tests mutate them. We try
|
|
// not to but there are exceptions.
|
|
const featureFlags = require('shared/ReactFeatureFlags');
|
|
const schedulerFeatureFlags = require('scheduler/src/SchedulerFeatureFlags');
|
|
|
|
const www = global.__WWW__ === true;
|
|
const releaseChannel = www
|
|
? __EXPERIMENTAL__
|
|
? 'modern'
|
|
: 'classic'
|
|
: __EXPERIMENTAL__
|
|
? 'experimental'
|
|
: 'stable';
|
|
|
|
// Return a proxy so we can throw if you attempt to access a flag that
|
|
// doesn't exist.
|
|
return new Proxy(
|
|
{
|
|
// Feature flag aliases
|
|
old: featureFlags.enableNewReconciler === false,
|
|
new: featureFlags.enableNewReconciler === true,
|
|
|
|
channel: releaseChannel,
|
|
modern: releaseChannel === 'modern',
|
|
classic: releaseChannel === 'classic',
|
|
source: !process.env.IS_BUILD,
|
|
www,
|
|
|
|
// This isn't a flag, just a useful alias for tests. Remove once
|
|
// useSyncExternalStore lands in the `next` channel.
|
|
supportsNativeUseSyncExternalStore: __EXPERIMENTAL__ || www,
|
|
|
|
// If there's a naming conflict between scheduler and React feature flags, the
|
|
// React ones take precedence.
|
|
// TODO: Maybe we should error on conflicts? Or we could namespace
|
|
// the flags
|
|
...schedulerFeatureFlags,
|
|
...featureFlags,
|
|
...environmentFlags,
|
|
},
|
|
{
|
|
get(flags, flagName) {
|
|
const flagValue = flags[flagName];
|
|
if (flagValue === undefined && typeof flagName === 'string') {
|
|
throw Error(
|
|
`Feature flag "${flagName}" does not exist. See TestFlags.js ` +
|
|
'for more details.'
|
|
);
|
|
}
|
|
return flagValue;
|
|
},
|
|
}
|
|
);
|
|
}
|
|
|
|
exports.getTestFlags = getTestFlags;
|