mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
* Add LanePriority type
React's internal scheduler has more priority levels than the external
Scheduler package. Let's use React as the source of truth for tracking
the priority of updates so we have more control. We'll still fall back
to Scheduler in the default case. In the future, we should consider
removing `runWithPriority` from Scheduler and replacing the valid use
cases with React-specific APIs.
This commit adds a new type, called a LanePriority to disambiguate from
the Scheduler one.
("Lane" refers to another type that I'm planning. It roughly translates
to "thread." Each lane will have a priority associated with it.)
I'm not actually using the lane anywhere, yet. Only setting stuff up.
* Remove expiration times train model
In the old reconciler, expiration times are computed by applying an
offset to the current system time. This has the effect of increasing
the priority of updates as time progresses. Because we also use
expiration times as a kind of "thread" identifier, it turns out this
is quite limiting because we can only flush work sequentially along
the timeline.
The new model will use a bitmask to represent parallel threads that
can be worked on in any combination and in any order.
In this commit, expiration times and the linear timeline are still in
place, but they are no longer based on a timestamp. Effectively, they
are constants based on their priority level.
* Stop using ExpirationTime to represent timestamps
Follow up to the previous commit. This converts the remaining places
where we were using the ExpirationTime type to represent a timestamp,
like Suspense timeouts.
* Fork Dependencies and PendingInteractionMap types
These contain expiration times
* Make ExpirationTime an opaque type
ExpirationTime is currently just an alias for the `number` type, for a
few reasons. One is that it predates Flow's opaque type feature. Another
is that making it opaque means we have to move all our comparisons and
number math to the ExpirationTime module, and use utility functions
everywhere else.
However, this is actually what we want in the new system, because the
Lanes type that will replace ExpirationTime is a bitmask with a
particular layout, and performing operations on it will involve more
than just number comparisions and artihmetic. I don't want this logic to
spread ad hoc around the whole codebase.
The utility functions get inlined by Closure so it doesn't matter
performance-wise.
I automated most of the changes with JSCodeshift, with only a few manual
tweaks to stuff like imports. My goal was to port the logic exactly to
prevent subtle mistakes, without trying to simplify anything in the
process. I'll likely need to audit many of these sites again when I
replace them with the new type, though, especially the ones
in ReactFiberRoot.
I added the codemods I used to the `scripts` directory. I won't merge
these to master. I'll remove them in a subsequent commit. I'm only
committing them here so they show up in the PR for future reference.
I had a lot of trouble getting Flow to pass. Somehow it was not
inferring the correct type of the constants exported from the
ExpirationTime module, despite being annotated correctly.
I tried converting them them to constructor functions — `NoWork`
becomes `NoWork()` — and that made it work. I used that to unblock me,
and fixed all the other type errors. Once there were no more type
errors, I tried converting the constructors back to constants. Started
getting errors again.
Then I added a type constraint everywhere a constant was referenced.
That fixed it. I also figured out that you only have to add a constraint
when the constant is passed to another function, even if the function is
annotated. So this indicates to me that it's probably a Flow bug. I'll
file an issue with Flow.
* Delete temporary codemods used in previous commit
I only added these to the previous commit so that I can easily run it
again when rebasing. When the stack is squashed, it will be as if they
never existed.
123 lines
3.4 KiB
JavaScript
123 lines
3.4 KiB
JavaScript
/**
|
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
* @flow
|
|
*/
|
|
|
|
import {enableProfilerTimer} from 'shared/ReactFeatureFlags';
|
|
|
|
import type {Fiber, FiberRoot, ReactPriorityLevel} from './ReactInternalTypes';
|
|
import type {ReactNodeList} from 'shared/ReactTypes';
|
|
|
|
import {DidCapture} from './ReactSideEffectTags';
|
|
|
|
declare var __REACT_DEVTOOLS_GLOBAL_HOOK__: Object | void;
|
|
|
|
let rendererID = null;
|
|
let injectedHook = null;
|
|
let hasLoggedError = false;
|
|
|
|
export const isDevToolsPresent =
|
|
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== 'undefined';
|
|
|
|
export function injectInternals(internals: Object): boolean {
|
|
if (typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ === 'undefined') {
|
|
// No DevTools
|
|
return false;
|
|
}
|
|
const hook = __REACT_DEVTOOLS_GLOBAL_HOOK__;
|
|
if (hook.isDisabled) {
|
|
// This isn't a real property on the hook, but it can be set to opt out
|
|
// of DevTools integration and associated warnings and logs.
|
|
// https://github.com/facebook/react/issues/3877
|
|
return true;
|
|
}
|
|
if (!hook.supportsFiber) {
|
|
if (__DEV__) {
|
|
console.error(
|
|
'The installed version of React DevTools is too old and will not work ' +
|
|
'with the current version of React. Please update React DevTools. ' +
|
|
'https://fb.me/react-devtools',
|
|
);
|
|
}
|
|
// DevTools exists, even though it doesn't support Fiber.
|
|
return true;
|
|
}
|
|
try {
|
|
rendererID = hook.inject(internals);
|
|
// We have successfully injected, so now it is safe to set up hooks.
|
|
injectedHook = hook;
|
|
} catch (err) {
|
|
// Catch all errors because it is unsafe to throw during initialization.
|
|
if (__DEV__) {
|
|
console.error('React instrumentation encountered an error: %s.', err);
|
|
}
|
|
}
|
|
// DevTools exists
|
|
return true;
|
|
}
|
|
|
|
export function onScheduleRoot(root: FiberRoot, children: ReactNodeList) {
|
|
if (__DEV__) {
|
|
if (
|
|
injectedHook &&
|
|
typeof injectedHook.onScheduleFiberRoot === 'function'
|
|
) {
|
|
try {
|
|
injectedHook.onScheduleFiberRoot(rendererID, root, children);
|
|
} catch (err) {
|
|
if (__DEV__ && !hasLoggedError) {
|
|
hasLoggedError = true;
|
|
console.error('React instrumentation encountered an error: %s', err);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
export function onCommitRoot(
|
|
root: FiberRoot,
|
|
priorityLevel: ReactPriorityLevel,
|
|
) {
|
|
if (injectedHook && typeof injectedHook.onCommitFiberRoot === 'function') {
|
|
try {
|
|
const didError = (root.current.effectTag & DidCapture) === DidCapture;
|
|
if (enableProfilerTimer) {
|
|
injectedHook.onCommitFiberRoot(
|
|
rendererID,
|
|
root,
|
|
priorityLevel,
|
|
didError,
|
|
);
|
|
} else {
|
|
injectedHook.onCommitFiberRoot(rendererID, root, undefined, didError);
|
|
}
|
|
} catch (err) {
|
|
if (__DEV__) {
|
|
if (!hasLoggedError) {
|
|
hasLoggedError = true;
|
|
console.error('React instrumentation encountered an error: %s', err);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
export function onCommitUnmount(fiber: Fiber) {
|
|
if (injectedHook && typeof injectedHook.onCommitFiberUnmount === 'function') {
|
|
try {
|
|
injectedHook.onCommitFiberUnmount(rendererID, fiber);
|
|
} catch (err) {
|
|
if (__DEV__) {
|
|
if (!hasLoggedError) {
|
|
hasLoggedError = true;
|
|
console.error('React instrumentation encountered an error: %s', err);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|