Refactor inspect/select logic so that $r contains hooks data (#364)
* Refactor inspect/select logic so that var contains hooks data * Legacy renderer resets $r to null when inspecting non class/function element
Brian Vaughn committed
Aug 3, 2019 at 17:59 UTC
db8542ad97c75c9f61a949209f6c054a1c0c9688
7 files changed
+105
-110
packages/react-devtools-core/src/backend.js
+2
-2
@@ -112,8 +112,8 @@ export function connectToDevTools(options: ?ConnectOptions) {
112
},
113
});
114
bridge.addListener(
115
- 'selectElement',
116
- ({ id, rendererID }: {| id: number, rendererID: number |}) => {
115
+ 'inspectElement',
116
+ ({ id, rendererID }: { id: number, rendererID: number }) => {
117
const renderer = agent.rendererInterfaces[rendererID];
118
if (renderer != null) {
119
// Send event for RN to highlight.
src/backend/agent.js
+18
-28
@@ -124,7 +124,6 @@ export default class Agent extends EventEmitter<{|
124
bridge.addListener('overrideState', this.overrideState);
125
bridge.addListener('overrideSuspense', this.overrideSuspense);
126
bridge.addListener('reloadAndProfile', this.reloadAndProfile);
127
- bridge.addListener('selectElement', this.selectElement);
127
bridge.addListener('startProfiling', this.startProfiling);
128
bridge.addListener('stopProfiling', this.stopProfiling);
129
bridge.addListener(
@@ -219,6 +218,24 @@ export default class Agent extends EventEmitter<{|
218
console.warn(`Invalid renderer id "${rendererID}" for element "${id}"`);
219
} else {
220
this._bridge.send('inspectedElement', renderer.inspectElement(id, path));
221
+
222
+ // When user selects an element, stop trying to restore the selection,
223
+ // and instead remember the current selection for the next reload.
224
+ if (
225
+ this._persistedSelectionMatch === null ||
226
+ this._persistedSelectionMatch.id !== id
227
+ ) {
228
+ this._persistedSelection = null;
229
+ this._persistedSelectionMatch = null;
230
+ renderer.setTrackedPath(null);
231
+ this._throttledPersistSelection(rendererID, id);
232
+ }
233
+
234
+ // TODO: If there was a way to change the selected DOM element
235
+ // in native Elements tab without forcing a switch to it, we'd do it here.
236
+ // For now, it doesn't seem like there is a way to do that:
237
+ // https://github.com/bvaughn/react-devtools-experimental/issues/102
238
+ // (Setting $0 doesn't work, and calling inspect() switches the tab.)
239
}
240
};
241
@@ -244,33 +261,6 @@ export default class Agent extends EventEmitter<{|
261
this._bridge.send('reloadAppForProfiling');
262
};
263
247
- selectElement = ({ id, rendererID }: ElementAndRendererID) => {
248
- const renderer = this._rendererInterfaces[rendererID];
249
- if (renderer == null) {
250
- console.warn(`Invalid renderer id "${rendererID}" for element "${id}"`);
251
- } else {
252
- renderer.selectElement(id);
253
-
254
- // When user selects an element, stop trying to restore the selection,
255
- // and instead remember the current selection for the next reload.
256
- if (
257
- this._persistedSelectionMatch === null ||
258
- this._persistedSelectionMatch.id !== id
259
- ) {
260
- this._persistedSelection = null;
261
- this._persistedSelectionMatch = null;
262
- renderer.setTrackedPath(null);
263
- this._throttledPersistSelection(rendererID, id);
264
- }
265
-
266
- // TODO: If there was a way to change the selected DOM element
267
- // in native Elements tab without forcing a switch to it, we'd do it here.
268
- // For now, it doesn't seem like there is a way to do that:
269
- // https://github.com/bvaughn/react-devtools-experimental/issues/102
270
- // (Setting $0 doesn't work, and calling inspect() switches the tab.)
271
- }
272
- };
273
-
264
overrideContext = ({ id, path, rendererID, value }: SetInParams) => {
265
const renderer = this._rendererInterfaces[rendererID];
266
if (renderer == null) {
src/backend/legacy/renderer.js
+34
-29
@@ -609,6 +609,35 @@ export function attach(
609
};
610
}
611
612
+ function updateSelectedElement(id: number): void {
613
+ const internalInstance = idToInternalInstanceMap.get(id);
614
+ if (internalInstance == null) {
615
+ console.warn(`Could not find instance with id "${id}"`);
616
+ return;
617
+ }
618
+
619
+ switch (getElementType(internalInstance)) {
620
+ case ElementTypeClass:
621
+ global.$r = internalInstance._instance;
622
+ break;
623
+ case ElementTypeFunction:
624
+ const element = internalInstance._currentElement;
625
+ if (element == null) {
626
+ console.warn(`Could not find element with id "${id}"`);
627
+ return;
628
+ }
629
+
630
+ global.$r = {
631
+ props: element.props,
632
+ type: element.type,
633
+ };
634
+ break;
635
+ default:
636
+ global.$r = null;
637
+ break;
638
+ }
639
+ }
640
+
641
function inspectElement(
642
id: number,
643
path?: Array<string | number>
@@ -630,6 +659,11 @@ export function attach(
659
mergeInspectedPaths(path);
660
}
661
662
+ // Any time an inspected element has an update,
663
+ // we should update the selected $r value as wel.
664
+ // Do this before dehyration (cleanForBridge).
665
+ updateSelectedElement(id);
666
+
667
inspectedElement.context = cleanForBridge(
668
inspectedElement.context,
669
createIsPathWhitelisted('context')
@@ -777,34 +811,6 @@ export function attach(
811
global.$type = element.type;
812
}
813
780
- function selectElement(id: number): void {
781
- const internalInstance = idToInternalInstanceMap.get(id);
782
- if (internalInstance == null) {
783
- console.warn(`Could not find instance with id "${id}"`);
784
- return;
785
- }
786
-
787
- switch (getElementType(internalInstance)) {
788
- case ElementTypeClass:
789
- global.$r = internalInstance._instance;
790
- break;
791
- case ElementTypeFunction:
792
- const element = internalInstance._currentElement;
793
- if (element == null) {
794
- console.warn(`Could not find element with id "${id}"`);
795
- return;
796
- }
797
-
798
- global.$r = {
799
- props: element.props,
800
- type: element.type,
801
- };
802
- break;
803
- default:
804
- break;
805
- }
806
- }
807
-
814
function setInProps(id: number, path: Array<string | number>, value: any) {
815
const internalInstance = idToInternalInstanceMap.get(id);
816
if (internalInstance != null) {
@@ -918,7 +924,6 @@ export function attach(
924
overrideSuspense,
925
prepareViewElementSource,
926
renderer,
921
- selectElement,
927
setInContext,
928
setInHook,
929
setInProps,
src/backend/renderer.js
+51
-44
@@ -1988,49 +1988,6 @@ export function attach(
1988
}
1989
// END copied code
1990
1991
- function selectElement(id: number): void {
1992
- let fiber = idToFiberMap.get(id);
1993
- if (fiber == null) {
1994
- console.warn(`Could not find Fiber with id "${id}"`);
1995
- return;
1996
- }
1997
-
1998
- const { elementType, memoizedProps, stateNode, tag, type } = fiber;
1999
-
2000
- switch (tag) {
2001
- case ClassComponent:
2002
- case IncompleteClassComponent:
2003
- case IndeterminateComponent:
2004
- global.$r = stateNode;
2005
- break;
2006
- case FunctionComponent:
2007
- global.$r = {
2008
- props: memoizedProps,
2009
- type,
2010
- };
2011
- break;
2012
- case ForwardRef:
2013
- global.$r = {
2014
- props: memoizedProps,
2015
- type: type.render,
2016
- };
2017
- break;
2018
- case MemoComponent:
2019
- case SimpleMemoComponent:
2020
- global.$r = {
2021
- props: memoizedProps,
2022
- type:
2023
- elementType != null && elementType.type != null
2024
- ? elementType.type
2025
- : type,
2026
- };
2027
- break;
2028
- default:
2029
- global.$r = null;
2030
- break;
2031
- }
2032
- }
2033
-
1991
function prepareViewElementSource(id: number): void {
1992
let fiber = idToFiberMap.get(id);
1993
if (fiber == null) {
@@ -2341,6 +2298,52 @@ export function attach(
2298
};
2299
}
2300
2301
+ function updateSelectedElement(inspectedElement: InspectedElement): void {
2302
+ const { hooks, id, props } = inspectedElement;
2303
+
2304
+ let fiber = idToFiberMap.get(id);
2305
+ if (fiber == null) {
2306
+ console.warn(`Could not find Fiber with id "${id}"`);
2307
+ return;
2308
+ }
2309
+
2310
+ const { elementType, stateNode, tag, type } = fiber;
2311
+
2312
+ switch (tag) {
2313
+ case ClassComponent:
2314
+ case IncompleteClassComponent:
2315
+ case IndeterminateComponent:
2316
+ global.$r = stateNode;
2317
+ break;
2318
+ case FunctionComponent:
2319
+ global.$r = {
2320
+ hooks,
2321
+ props,
2322
+ type,
2323
+ };
2324
+ break;
2325
+ case ForwardRef:
2326
+ global.$r = {
2327
+ props,
2328
+ type: type.render,
2329
+ };
2330
+ break;
2331
+ case MemoComponent:
2332
+ case SimpleMemoComponent:
2333
+ global.$r = {
2334
+ props,
2335
+ type:
2336
+ elementType != null && elementType.type != null
2337
+ ? elementType.type
2338
+ : type,
2339
+ };
2340
+ break;
2341
+ default:
2342
+ global.$r = null;
2343
+ break;
2344
+ }
2345
+ }
2346
+
2347
function inspectElement(
2348
id: number,
2349
path?: Array<string | number>
@@ -2401,6 +2404,11 @@ export function attach(
2404
mergeInspectedPaths(path);
2405
}
2406
2407
+ // Any time an inspected element has an update,
2408
+ // we should update the selected $r value as wel.
2409
+ // Do this before dehyration (cleanForBridge).
2410
+ updateSelectedElement(mostRecentlyInspectedElement);
2411
+
2412
// Clone before cleaning so that we preserve the full data.
2413
// This will enable us to send patches without re-inspecting if hydrated paths are requested.
2414
// (Reducing how often we shallow-render is a better DX for function components that use hooks.)
@@ -2986,7 +2994,6 @@ export function attach(
2994
prepareViewElementSource,
2995
overrideSuspense,
2996
renderer,
2989
- selectElement,
2997
setInContext,
2998
setInHook,
2999
setInProps,
src/backend/types.js
-1
@@ -299,7 +299,6 @@ export type RendererInterface = {
299
overrideSuspense: (id: number, forceFallback: boolean) => void,
300
prepareViewElementSource: (id: number) => void,
301
renderer: ReactRenderer | null,
302
- selectElement: (id: number) => void,
302
setInContext: (id: number, path: Array<string | number>, value: any) => void,
303
setInHook: (
304
id: number,
src/bridge.js
-1
@@ -99,7 +99,6 @@ type FrontendEvents = {|
99
overrideSuspense: [OverrideSuspense],
100
profilingData: [ProfilingDataBackend],
101
reloadAndProfile: [boolean],
102
- selectElement: [ElementAndRendererID],
102
selectFiber: [number],
103
shutdown: [],
104
startInspectingNative: [],
src/devtools/views/Components/InspectedElementContext.js
-5
@@ -241,11 +241,6 @@ function InspectedElementContextController({ children }: Props) {
241
// We'll poll for an update in the response handler below.
242
sendRequest();
243
244
- // Update the $r variable.
245
- if (rendererID !== null) {
246
- bridge.send('selectElement', { id: selectedElementID, rendererID });
247
- }
248
-
244
const onInspectedElement = (data: InspectedElementPayload) => {
245
// If this is the element we requested, wait a little bit and then ask for another update.
246
if (data.id === selectedElementID) {