Allow to toggle Suspense in Components pane

This commit is contained in:
Dan Abramov
2019-04-05 13:19:30 +01:00
parent bfbb4ac545
commit 4f5437edf7
6 changed files with 119 additions and 8 deletions
+1
View File
@@ -32,6 +32,7 @@ function mountTestApp() {
mountHelper(ElementTypes);
mountHelper(EditableProps);
mountHelper(DeeplyNestedComponents);
mountHelper(SuspenseTree);
}
function unmountTestApp() {
+20
View File
@@ -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
View File
@@ -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,
+5
View File
@@ -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"
+3
View File
@@ -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,