mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Allow to toggle Suspense in Components pane
This commit is contained in:
@@ -32,6 +32,7 @@ function mountTestApp() {
|
||||
mountHelper(ElementTypes);
|
||||
mountHelper(EditableProps);
|
||||
mountHelper(DeeplyNestedComponents);
|
||||
mountHelper(SuspenseTree);
|
||||
}
|
||||
|
||||
function unmountTestApp() {
|
||||
|
||||
@@ -38,6 +38,12 @@ type SetInParams = {|
|
||||
value: any,
|
||||
|};
|
||||
|
||||
type OverrideSuspenseParams = {|
|
||||
id: number,
|
||||
rendererID: number,
|
||||
forceFallback: boolean,
|
||||
|};
|
||||
|
||||
export default class Agent extends EventEmitter {
|
||||
_bridge: Bridge = ((null: any): Bridge);
|
||||
_isProfiling: boolean = false;
|
||||
@@ -68,6 +74,7 @@ export default class Agent extends EventEmitter {
|
||||
bridge.addListener('overrideHookState', this.overrideHookState);
|
||||
bridge.addListener('overrideProps', this.overrideProps);
|
||||
bridge.addListener('overrideState', this.overrideState);
|
||||
bridge.addListener('overrideSuspense', this.overrideSuspense);
|
||||
bridge.addListener('reloadAndProfile', this.reloadAndProfile);
|
||||
bridge.addListener('screenshotCaptured', this.screenshotCaptured);
|
||||
bridge.addListener('selectElement', this.selectElement);
|
||||
@@ -302,6 +309,19 @@ export default class Agent extends EventEmitter {
|
||||
}
|
||||
};
|
||||
|
||||
overrideSuspense = ({
|
||||
id,
|
||||
rendererID,
|
||||
forceFallback,
|
||||
}: OverrideSuspenseParams) => {
|
||||
const renderer = this._rendererInterfaces[rendererID];
|
||||
if (renderer == null) {
|
||||
console.warn(`Invalid renderer id "${rendererID}" for element "${id}"`);
|
||||
} else {
|
||||
renderer.overrideSuspense(id, forceFallback);
|
||||
}
|
||||
};
|
||||
|
||||
setRendererInterface(
|
||||
rendererID: RendererID,
|
||||
rendererInterface: RendererInterface
|
||||
|
||||
+62
-3
@@ -199,6 +199,7 @@ export function attach(
|
||||
IndeterminateComponent,
|
||||
MemoComponent,
|
||||
SimpleMemoComponent,
|
||||
SuspenseComponent,
|
||||
} = ReactTypeOfWork;
|
||||
const {
|
||||
CONCURRENT_MODE_NUMBER,
|
||||
@@ -219,7 +220,15 @@ export function attach(
|
||||
DEPRECATED_PLACEHOLDER_SYMBOL_STRING,
|
||||
} = ReactSymbols;
|
||||
|
||||
const { overrideHookState, overrideProps } = renderer;
|
||||
const {
|
||||
overrideHookState,
|
||||
overrideProps,
|
||||
setSuspenseHandler,
|
||||
scheduleUpdate,
|
||||
} = renderer;
|
||||
const supportsEditingSuspense =
|
||||
typeof setSuspenseHandler === 'function' &&
|
||||
typeof scheduleUpdate === 'function';
|
||||
|
||||
const debug = (name: string, fiber: Fiber, parentFiber: ?Fiber): void => {
|
||||
if (__DEBUG__) {
|
||||
@@ -845,8 +854,7 @@ export function attach(
|
||||
|
||||
// Suspense components only have a non-null memoizedState if they're timed-out.
|
||||
const isTimedOutSuspense =
|
||||
nextFiber.tag === ReactTypeOfWork.SuspenseComponent &&
|
||||
nextFiber.memoizedState !== null;
|
||||
nextFiber.tag === SuspenseComponent && nextFiber.memoizedState !== null;
|
||||
|
||||
if (isTimedOutSuspense) {
|
||||
// The behavior of timed-out Suspense trees is unique.
|
||||
@@ -1411,6 +1419,9 @@ export function attach(
|
||||
}
|
||||
}
|
||||
|
||||
const isTimedOutSuspense =
|
||||
tag === SuspenseComponent && memoizedState !== null;
|
||||
|
||||
return {
|
||||
id,
|
||||
|
||||
@@ -1420,6 +1431,14 @@ export function attach(
|
||||
// Does the current renderer support editable function props?
|
||||
canEditFunctionProps: typeof overrideProps === 'function',
|
||||
|
||||
canEditSuspense:
|
||||
supportsEditingSuspense &&
|
||||
// If it's showing the real content, we can always flip fallback.
|
||||
(!isTimedOutSuspense ||
|
||||
// If it's showing fallback because we previously forced it to,
|
||||
// allow toggling it back to remove the fallback override.
|
||||
forceFallbackForSuspenseIDs.has(id)),
|
||||
|
||||
// Can view component source location.
|
||||
canViewSource,
|
||||
|
||||
@@ -1667,6 +1686,45 @@ export function attach(
|
||||
startProfiling();
|
||||
}
|
||||
|
||||
// React will switch between these implementations depending on whether
|
||||
// we have any manually suspended Fibers or not.
|
||||
|
||||
function shouldSuspendFiberAlwaysFalse() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let forceFallbackForSuspenseIDs = new Set();
|
||||
function shouldSuspendFiberAccordingToSet(fiber) {
|
||||
const id = getFiberID(getPrimaryFiber(((fiber: any): Fiber)));
|
||||
return forceFallbackForSuspenseIDs.has(id);
|
||||
}
|
||||
|
||||
function overrideSuspense(id, forceFallback) {
|
||||
if (
|
||||
typeof setSuspenseHandler !== 'function' ||
|
||||
typeof scheduleUpdate !== 'function'
|
||||
) {
|
||||
throw new Error(
|
||||
'Expected overrideSuspense() to not get called for earlier React versions.'
|
||||
);
|
||||
}
|
||||
if (forceFallback) {
|
||||
forceFallbackForSuspenseIDs.add(id);
|
||||
if (forceFallbackForSuspenseIDs.size === 1) {
|
||||
// First override is added. Switch React to slower path.
|
||||
setSuspenseHandler(shouldSuspendFiberAccordingToSet);
|
||||
}
|
||||
} else {
|
||||
forceFallbackForSuspenseIDs.delete(id);
|
||||
if (forceFallbackForSuspenseIDs.size === 0) {
|
||||
// Last override is gone. Switch React back to fast path.
|
||||
setSuspenseHandler(shouldSuspendFiberAlwaysFalse);
|
||||
}
|
||||
}
|
||||
const fiber = idToFiberMap.get(id);
|
||||
scheduleUpdate(fiber);
|
||||
}
|
||||
|
||||
return {
|
||||
cleanup,
|
||||
flushInitialOperations,
|
||||
@@ -1680,6 +1738,7 @@ export function attach(
|
||||
handleCommitFiberUnmount,
|
||||
inspectElement,
|
||||
prepareViewElementSource,
|
||||
overrideSuspense,
|
||||
renderer,
|
||||
selectElement,
|
||||
setInContext,
|
||||
|
||||
@@ -45,6 +45,10 @@ export type ReactRenderer = {
|
||||
value: any
|
||||
) => void,
|
||||
|
||||
// 16.9+
|
||||
scheduleUpdate?: ?(fiber: Object) => void,
|
||||
setSuspenseHandler?: ?(shouldSuspend: (fiber: Object) => boolean) => void,
|
||||
|
||||
// Only injected by React v16.8+ in order to support hooks inspection.
|
||||
currentDispatcherRef?: {| current: null | Dispatcher |},
|
||||
};
|
||||
@@ -95,6 +99,7 @@ export type RendererInterface = {
|
||||
handleCommitFiberRoot: (fiber: Object) => void,
|
||||
handleCommitFiberUnmount: (fiber: Object) => void,
|
||||
inspectElement: (id: number) => InspectedElement | null,
|
||||
overrideSuspense: (id: number, forceFallback: boolean) => void,
|
||||
prepareViewElementSource: (id: number) => void,
|
||||
renderer: ReactRenderer | null,
|
||||
selectElement: (id: number) => void,
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
ElementTypeForwardRef,
|
||||
ElementTypeFunction,
|
||||
ElementTypeMemo,
|
||||
ElementTypeSuspense,
|
||||
} from '../../types';
|
||||
|
||||
import type { InspectedElement } from './types';
|
||||
@@ -115,6 +116,8 @@ type InspectedElementViewProps = {|
|
||||
inspectedElement: InspectedElement,
|
||||
|};
|
||||
|
||||
const IS_SUSPENDED = 'Suspended';
|
||||
|
||||
function InspectedElementView({
|
||||
element,
|
||||
inspectedElement,
|
||||
@@ -123,6 +126,7 @@ function InspectedElementView({
|
||||
const {
|
||||
canEditFunctionProps,
|
||||
canEditHooks,
|
||||
canEditSuspense,
|
||||
context,
|
||||
hooks,
|
||||
owners,
|
||||
@@ -137,6 +141,7 @@ function InspectedElementView({
|
||||
let overrideContextFn = null;
|
||||
let overridePropsFn = null;
|
||||
let overrideStateFn = null;
|
||||
let overrideSuspenseFn = null;
|
||||
if (type === ElementTypeClass) {
|
||||
overrideContextFn = (path: Array<string | number>, value: any) => {
|
||||
const rendererID = store.getRendererIDForElement(id);
|
||||
@@ -160,6 +165,14 @@ function InspectedElementView({
|
||||
const rendererID = store.getRendererIDForElement(id);
|
||||
bridge.send('overrideProps', { id, path, rendererID, value });
|
||||
};
|
||||
} else if (type === ElementTypeSuspense && canEditSuspense) {
|
||||
overrideSuspenseFn = (path: Array<string | number>, value: boolean) => {
|
||||
if (path.length !== 1 && path !== IS_SUSPENDED) {
|
||||
throw new Error('Unexpected path.');
|
||||
}
|
||||
const rendererID = store.getRendererIDForElement(id);
|
||||
bridge.send('overrideSuspense', { id, rendererID, forceFallback: value });
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -170,11 +183,21 @@ function InspectedElementView({
|
||||
overrideValueFn={overridePropsFn}
|
||||
showWhenEmpty
|
||||
/>
|
||||
<InspectedElementTree
|
||||
label="state"
|
||||
data={state}
|
||||
overrideValueFn={overrideStateFn}
|
||||
/>
|
||||
{type === ElementTypeSuspense ? (
|
||||
<InspectedElementTree
|
||||
label="suspense"
|
||||
data={{
|
||||
[IS_SUSPENDED]: state !== null,
|
||||
}}
|
||||
overrideValueFn={overrideSuspenseFn}
|
||||
/>
|
||||
) : (
|
||||
<InspectedElementTree
|
||||
label="state"
|
||||
data={state}
|
||||
overrideValueFn={overrideStateFn}
|
||||
/>
|
||||
)}
|
||||
<HooksTree canEditHooks={canEditHooks} hooks={hooks} id={id} />
|
||||
<InspectedElementTree
|
||||
label="context"
|
||||
|
||||
@@ -41,6 +41,9 @@ export type InspectedElement = {|
|
||||
// Does the current renderer support editable function props?
|
||||
canEditFunctionProps: boolean,
|
||||
|
||||
// Is this Suspense, and can its value be overriden now?
|
||||
canEditSuspense: boolean,
|
||||
|
||||
// Can view component source location.
|
||||
canViewSource: boolean,
|
||||
|
||||
|
||||
Reference in New Issue
Block a user