mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Merge pull request #61 from gaearon/toggle-suspense-2
Allow to toggle Suspense in Components pane
This commit is contained in:
+22
-5
@@ -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);
|
||||
@@ -203,11 +210,8 @@ export default class Agent extends EventEmitter {
|
||||
}
|
||||
|
||||
let node: HTMLElement | null = null;
|
||||
if (
|
||||
renderer !== null &&
|
||||
typeof renderer.getNativeFromReactElement === 'function'
|
||||
) {
|
||||
node = ((renderer.getNativeFromReactElement(id): any): HTMLElement);
|
||||
if (renderer !== null) {
|
||||
node = ((renderer.findNativeByFiberID(id): any): HTMLElement);
|
||||
}
|
||||
|
||||
if (node != null) {
|
||||
@@ -302,6 +306,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
|
||||
|
||||
+81
-10
@@ -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 supportsTogglingSuspense =
|
||||
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.
|
||||
@@ -1045,18 +1053,30 @@ export function attach(
|
||||
currentRootID = -1;
|
||||
}
|
||||
|
||||
// The naming is confusing.
|
||||
// They deal with opaque nodes (fibers), not elements.
|
||||
function getNativeFromReactElement(id: number) {
|
||||
function findNativeByFiberID(id: number) {
|
||||
try {
|
||||
const primaryFiber = getPrimaryFiber(idToFiberMap.get(id));
|
||||
const hostInstance = renderer.findHostInstanceByFiber(primaryFiber);
|
||||
return hostInstance;
|
||||
const fiber = findCurrentFiberUsingSlowPath(idToFiberMap.get(id));
|
||||
if (fiber === null) {
|
||||
return null;
|
||||
}
|
||||
const isTimedOutSuspense =
|
||||
fiber.tag === SuspenseComponent && fiber.memoizedState !== null;
|
||||
if (!isTimedOutSuspense) {
|
||||
// Normal case.
|
||||
return renderer.findHostInstanceByFiber(fiber);
|
||||
} else {
|
||||
// A timed-out Suspense's findDOMNode is useless.
|
||||
// Try our best to find the fallback directly.
|
||||
const maybeFallbackFiber =
|
||||
(fiber.child && fiber.child.sibling) || fiber;
|
||||
return renderer.findHostInstanceByFiber(maybeFallbackFiber);
|
||||
}
|
||||
} catch (err) {
|
||||
// The fiber might have unmounted by now.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function getFiberIDFromNative(
|
||||
hostInstance,
|
||||
findNearestUnfilteredAncestor = false
|
||||
@@ -1411,6 +1431,9 @@ export function attach(
|
||||
}
|
||||
}
|
||||
|
||||
const isTimedOutSuspense =
|
||||
tag === SuspenseComponent && memoizedState !== null;
|
||||
|
||||
return {
|
||||
id,
|
||||
|
||||
@@ -1420,6 +1443,14 @@ export function attach(
|
||||
// Does the current renderer support editable function props?
|
||||
canEditFunctionProps: typeof overrideProps === 'function',
|
||||
|
||||
canToggleSuspense:
|
||||
supportsTogglingSuspense &&
|
||||
// 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,19 +1698,59 @@ 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,
|
||||
getCommitDetails,
|
||||
getFiberIDFromNative,
|
||||
getInteractions,
|
||||
getNativeFromReactElement,
|
||||
findNativeByFiberID,
|
||||
getProfilingDataForDownload,
|
||||
getProfilingSummary,
|
||||
handleCommitFiberRoot,
|
||||
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 |},
|
||||
};
|
||||
@@ -82,9 +86,9 @@ export type ProfilingSummary = {|
|
||||
|
||||
export type RendererInterface = {
|
||||
cleanup: () => void,
|
||||
findNativeByFiberID: (id: number) => ?NativeType,
|
||||
flushInitialOperations: () => void,
|
||||
getCommitDetails: (rootID: number, commitIndex: number) => CommitDetails,
|
||||
getNativeFromReactElement?: ?(component: Fiber) => ?NativeType,
|
||||
getFiberIDFromNative: (
|
||||
component: NativeType,
|
||||
findNearestUnfilteredAncestor?: boolean
|
||||
@@ -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,
|
||||
canToggleSuspense,
|
||||
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 && canToggleSuspense) {
|
||||
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?
|
||||
canToggleSuspense: boolean,
|
||||
|
||||
// Can view component source location.
|
||||
canViewSource: boolean,
|
||||
|
||||
|
||||
Reference in New Issue
Block a user