Refactored TreeContext to use less memoization (based on feedback from Sebastian)
Brian Vaughn committed
Apr 22, 2019 at 10:19 UTC
d53ae2ea8a5db88232c6802fc8f480cb13c23576
10 files changed
+268
-321
src/devtools/views/Components/Element.js
+21
-13
@@ -13,7 +13,7 @@ import { ElementTypeClass, ElementTypeFunction } from 'src/devtools/types';
13
import Store from 'src/devtools/store';
14
import ButtonIcon from '../ButtonIcon';
15
import { createRegExp } from '../utils';
16
-import { TreeContext } from './TreeContext';
16
+import { TreeDispatcherContext, TreeStateContext } from './TreeContext';
17
import { StoreContext } from '../context';
18
19
import type { ItemData } from './Tree';
@@ -28,18 +28,21 @@ type Props = {
28
};
29
30
export default function ElementView({ data, index, style }: Props) {
31
- const [isHovered, setIsHovered] = useState(false);
31
+ const store = useContext(StoreContext);
32
const {
33
baseDepth,
34
- getElementAtIndex,
34
+ ownerFlatTree,
35
ownerStack,
36
- selectOwner,
36
selectedElementID,
38
- selectElementByID,
39
- } = useContext(TreeContext);
40
- const store = useContext(StoreContext);
37
+ } = useContext(TreeStateContext);
38
+ const dispatch = useContext(TreeDispatcherContext);
39
42
- const element = getElementAtIndex(index);
40
+ const element =
41
+ ownerFlatTree !== null
42
+ ? store.getElementByID(ownerFlatTree[index])
43
+ : store.getElementAtIndex(index);
44
+
45
+ const [isHovered, setIsHovered] = useState(false);
46
47
const {
48
lastScrolledIDRef,
@@ -52,9 +55,9 @@ export default function ElementView({ data, index, style }: Props) {
55
56
const handleDoubleClick = useCallback(() => {
57
if (id !== null) {
55
- selectOwner(id);
58
+ dispatch({ type: 'SELECT_OWNER', payload: id });
59
}
57
- }, [id, selectOwner]);
60
+ }, [dispatch, id]);
61
62
const scrollAnchorStartRef = useRef<HTMLSpanElement | null>(null);
63
const scrollAnchorEndRef = useRef<HTMLSpanElement | null>(null);
@@ -102,10 +105,13 @@ export default function ElementView({ data, index, style }: Props) {
105
const handleMouseDown = useCallback(
106
({ metaKey }) => {
107
if (id !== null) {
105
- selectElementByID(metaKey ? null : id);
108
+ dispatch({
109
+ type: 'SELECT_ELEMENT_BY_ID',
110
+ payload: metaKey ? null : id,
111
+ });
112
}
113
},
108
- [id, selectElementByID]
114
+ [dispatch, id]
115
);
116
117
const handleMouseEnter = useCallback(() => {
@@ -234,7 +240,9 @@ type DisplayNameProps = {|
240
|};
241
242
function DisplayName({ displayName, id }: DisplayNameProps) {
237
- const { searchIndex, searchResults, searchText } = useContext(TreeContext);
243
+ const { searchIndex, searchResults, searchText } = useContext(
244
+ TreeStateContext
245
+ );
246
const isSearchResult = useMemo(() => {
247
return searchResults.includes(id);
248
}, [id, searchResults]);
src/devtools/views/Components/InspectedElementContext.js
+2
-2
@@ -10,7 +10,7 @@ import React, {
10
import { createResource } from '../../cache';
11
import { BridgeContext, StoreContext } from '../context';
12
import { hydrate } from 'src/hydration';
13
-import { TreeContext } from './TreeContext';
13
+import { TreeStateContext } from './TreeContext';
14
15
import type {
16
DehydratedData,
@@ -38,7 +38,7 @@ type Props = {|
38
function InspectedElementContextController({ children }: Props) {
39
const bridge = useContext(BridgeContext);
40
const store = useContext(StoreContext);
41
- const { inspectedElementID } = useContext(TreeContext);
41
+ const { inspectedElementID } = useContext(TreeStateContext);
42
43
const [count, setCount] = useState<number>(0);
44
src/devtools/views/Components/OwnersStack.js
+10
-10
@@ -11,7 +11,7 @@ import { Menu, MenuList, MenuButton, MenuItem } from '@reach/menu-button';
11
import Button from '../Button';
12
import ButtonIcon from '../ButtonIcon';
13
import Toggle from '../Toggle';
14
-import { TreeContext } from './TreeContext';
14
+import { TreeDispatcherContext, TreeStateContext } from './TreeContext';
15
import { StoreContext } from '../context';
16
import { useIsOverflowing } from '../hooks';
17
@@ -20,9 +20,8 @@ import type { Element } from './types';
20
import styles from './OwnersStack.css';
21
22
export default function OwnerStack() {
23
- const { ownerStack, ownerStackIndex, resetOwnerStack } = useContext(
24
- TreeContext
25
- );
23
+ const { ownerStack, ownerStackIndex } = useContext(TreeStateContext);
24
+ const dispatch = useContext(TreeDispatcherContext);
25
26
const [elementsTotalWidth, setElementsTotalWidth] = useState(0);
27
const elementsBarRef = useRef<HTMLDivElement | null>(null);
@@ -73,7 +72,7 @@ export default function OwnerStack() {
72
<div className={styles.VRule} />
73
<Button
74
className={styles.IconButton}
76
- onClick={resetOwnerStack}
75
+ onClick={() => dispatch({ type: 'RESET_OWNER_STACK' })}
76
title="Back to tree view"
77
>
78
<ButtonIcon type="close" />
@@ -91,7 +90,7 @@ function ElementsDropdown({
90
ownerStackIndex,
91
}: ElementsDropdownProps) {
92
const store = useContext(StoreContext);
94
- const { selectOwner } = useContext(TreeContext);
93
+ const dispatch = useContext(TreeDispatcherContext);
94
95
return (
96
<Menu>
@@ -107,7 +106,7 @@ function ElementsDropdown({
106
<MenuItem
107
key={id}
108
className={styles.Component}
110
- onSelect={() => selectOwner(id)}
109
+ onSelect={() => dispatch({ type: 'SELECT_OWNER', payload: id })}
110
>
111
{((store.getElementByID(id): any): Element).displayName}
112
</MenuItem>
@@ -123,7 +122,8 @@ type ElementViewProps = {
122
};
123
function ElementView({ id, index }: ElementViewProps) {
124
const store = useContext(StoreContext);
126
- const { ownerStackIndex, selectOwner } = useContext(TreeContext);
125
+ const { ownerStackIndex } = useContext(TreeStateContext);
126
+ const dispatch = useContext(TreeDispatcherContext);
127
128
const { displayName } = ((store.getElementByID(id): any): Element);
129
@@ -131,9 +131,9 @@ function ElementView({ id, index }: ElementViewProps) {
131
132
const handleChange = useCallback(() => {
133
if (!isChecked) {
134
- selectOwner(id);
134
+ dispatch({ type: 'SELECT_OWNER', payload: id });
135
}
136
- }, [id, isChecked, selectOwner]);
136
+ }, [dispatch, id, isChecked]);
137
138
return (
139
<Toggle
src/devtools/views/Components/SearchInput.js
+19
-21
@@ -1,7 +1,7 @@
1
// @flow
2
3
import React, { useCallback, useContext, useEffect, useRef } from 'react';
4
-import { TreeContext } from './TreeContext';
4
+import { TreeDispatcherContext, TreeStateContext } from './TreeContext';
5
import Button from '../Button';
6
import ButtonIcon from '../ButtonIcon';
7
import Icon from '../Icon';
@@ -11,51 +11,49 @@ import styles from './SearchInput.css';
11
type Props = {||};
12
13
export default function SearchInput(props: Props) {
14
- const {
15
- goToNextSearchResult,
16
- goToPreviousSearchResult,
17
- searchIndex,
18
- searchResults,
19
- searchText,
20
- selectNextElementInTree,
21
- selectPreviousElementInTree,
22
- setSearchText,
23
- } = useContext(TreeContext);
14
+ const { searchIndex, searchResults, searchText } = useContext(
15
+ TreeStateContext
16
+ );
17
+ const dispatch = useContext(TreeDispatcherContext);
18
19
const inputRef = useRef<HTMLInputElement | null>(null);
20
21
const handleTextChange = useCallback(
28
- ({ currentTarget }) => setSearchText(currentTarget.value),
29
- [setSearchText]
22
+ ({ currentTarget }) =>
23
+ dispatch({ type: 'SET_SEARCH_TEXT', payload: currentTarget.value }),
24
+ [dispatch]
25
+ );
26
+ const resetSearch = useCallback(
27
+ () => dispatch({ type: 'SET_SEARCH_TEXT', payload: '' }),
28
+ [dispatch]
29
);
31
- const resetSearch = useCallback(() => setSearchText(''), [setSearchText]);
30
31
const handleKeyDown = useCallback(
32
event => {
33
// For convenience, let up/down arrow keys change Tree selection.
34
switch (event.key) {
35
case 'ArrowDown':
38
- selectNextElementInTree();
36
+ dispatch({ type: 'SELECT_NEXT_ELEMENT_IN_TREE' });
37
event.preventDefault();
38
break;
39
case 'ArrowUp':
42
- selectPreviousElementInTree();
40
+ dispatch({ type: 'SELECT_PREVIOUS_ELEMENT_IN_TREE' });
41
event.preventDefault();
42
break;
43
default:
44
break;
45
}
46
},
49
- [selectNextElementInTree, selectPreviousElementInTree]
47
+ [dispatch]
48
);
49
50
const handleInputKeyPress = useCallback(
51
({ key }) => {
52
if (key === 'Enter') {
55
- goToNextSearchResult();
53
+ dispatch({ type: 'GO_TO_NEXT_SEARCH_RESULT' });
54
}
55
},
58
- [goToNextSearchResult]
56
+ [dispatch]
57
);
58
59
// Auto-focus search input
@@ -106,7 +104,7 @@ export default function SearchInput(props: Props) {
104
<Button
105
className={styles.IconButton}
106
disabled={!searchText}
109
- onClick={goToPreviousSearchResult}
107
+ onClick={() => dispatch({ type: 'GO_TO_PREVIOUS_SEARCH_RESULT' })}
108
title="Scroll to previous search result"
109
>
110
<ButtonIcon type="up" />
@@ -114,7 +112,7 @@ export default function SearchInput(props: Props) {
112
<Button
113
className={styles.IconButton}
114
disabled={!searchText}
117
- onClick={goToNextSearchResult}
115
+ onClick={() => dispatch({ type: 'GO_TO_NEXT_SEARCH_RESULT' })}
116
title="Scroll to next search result"
117
>
118
<ButtonIcon type="down" />
src/devtools/views/Components/SelectedElement.js
+14
-8
@@ -1,13 +1,14 @@
1
// @flow
2
3
import React, { useCallback, useContext } from 'react';
4
-import { TreeContext } from './TreeContext';
4
+import { TreeDispatcherContext, TreeStateContext } from './TreeContext';
5
import { BridgeContext, StoreContext } from '../context';
6
import Button from '../Button';
7
import ButtonIcon from '../ButtonIcon';
8
import HooksTree from './HooksTree';
9
import InspectedElementTree from './InspectedElementTree';
10
import { InspectedElementContext } from './InspectedElementContext';
11
+import ViewElementSourceContext from './ViewElementSourceContext';
12
import styles from './SelectedElement.css';
13
import {
14
ElementTypeClass,
@@ -22,7 +23,8 @@ import type { Element, InspectedElement } from './types';
23
export type Props = {||};
24
25
export default function SelectedElement(_: Props) {
25
- const { inspectedElementID, viewElementSource } = useContext(TreeContext);
26
+ const { inspectedElementID } = useContext(TreeStateContext);
27
+ const viewElementSource = useContext(ViewElementSourceContext);
28
const bridge = useContext(BridgeContext);
29
const store = useContext(StoreContext);
30
@@ -153,7 +155,7 @@ function InspectedElementView({
155
state,
156
} = inspectedElement;
157
156
- const { ownerStack } = useContext(TreeContext);
158
+ const { ownerStack } = useContext(TreeStateContext);
159
const bridge = useContext(BridgeContext);
160
const store = useContext(StoreContext);
161
@@ -241,12 +243,16 @@ function InspectedElementView({
243
}
244
245
function OwnerView({ displayName, id }: { displayName: string, id: number }) {
244
- const { selectElementByID } = useContext(TreeContext);
246
+ const dispatch = useContext(TreeDispatcherContext);
247
246
- const handleClick = useCallback(() => selectElementByID(id), [
247
- id,
248
- selectElementByID,
249
- ]);
248
+ const handleClick = useCallback(
249
+ () =>
250
+ dispatch({
251
+ type: 'SELECT_ELEMENT_BY_ID',
252
+ payload: id,
253
+ }),
254
+ [dispatch, id]
255
+ );
256
257
return (
258
<button
src/devtools/views/Components/Tree.js
+16
-31
@@ -11,7 +11,7 @@ import React, {
11
} from 'react';
12
import AutoSizer from 'react-virtualized-auto-sizer';
13
import { FixedSizeList } from 'react-window';
14
-import { TreeContext } from './TreeContext';
14
+import { TreeDispatcherContext, TreeStateContext } from './TreeContext';
15
import { SettingsContext } from '../Settings/SettingsContext';
16
import { BridgeContext, StoreContext } from '../context';
17
import ElementView from './Element';
@@ -21,12 +21,9 @@ import SearchInput from './SearchInput';
21
22
import styles from './Tree.css';
23
24
-import type { Element } from './types';
25
-
24
export type ItemData = {|
25
baseDepth: number,
26
numElements: number,
29
- getElementAtIndex: (index: number) => Element | null,
27
isNavigatingWithKeyboard: boolean,
28
lastScrolledIDRef: { current: number | null },
29
onElementMouseEnter: (id: number) => void,
@@ -36,22 +33,16 @@ export type ItemData = {|
33
type Props = {||};
34
35
export default function Tree(props: Props) {
36
+ const dispatch = useContext(TreeDispatcherContext);
37
const {
38
baseDepth,
41
- getElementAtIndex,
39
numElements,
40
ownerStack,
41
searchIndex,
42
searchResults,
43
selectedElementID,
44
selectedElementIndex,
48
- selectChildElementInTree,
49
- selectElementAtIndex,
50
- selectNextElementInTree,
51
- selectOwner,
52
- selectParentElementInTree,
53
- selectPreviousElementInTree,
54
- } = useContext(TreeContext);
45
+ } = useContext(TreeStateContext);
46
const bridge = useContext(BridgeContext);
47
const store = useContext(StoreContext);
48
const [isNavigatingWithKeyboard, setIsNavigatingWithKeyboard] = useState(
@@ -111,7 +102,7 @@ export default function Tree(props: Props) {
102
switch (event.key) {
103
case 'ArrowDown':
104
event.preventDefault();
114
- selectNextElementInTree();
105
+ dispatch({ type: 'SELECT_NEXT_ELEMENT_IN_TREE' });
106
break;
107
case 'ArrowLeft':
108
event.preventDefault();
@@ -123,7 +114,7 @@ export default function Tree(props: Props) {
114
if (element.children.length > 0 && !element.isCollapsed) {
115
store.toggleIsCollapsed(element.id, true);
116
} else {
126
- selectParentElementInTree();
117
+ dispatch({ type: 'SELECT_PARENT_ELEMENT_IN_TREE' });
118
}
119
}
120
break;
@@ -137,13 +128,13 @@ export default function Tree(props: Props) {
128
if (element.children.length > 0 && element.isCollapsed) {
129
store.toggleIsCollapsed(element.id, false);
130
} else {
140
- selectChildElementInTree();
131
+ dispatch({ type: 'SELECT_CHILD_ELEMENT_IN_TREE' });
132
}
133
}
134
break;
135
case 'ArrowUp':
136
event.preventDefault();
146
- selectPreviousElementInTree();
137
+ dispatch({ type: 'SELECT_PREVIOUS_ELEMENT_IN_TREE' });
138
break;
139
default:
140
return;
@@ -160,14 +151,7 @@ export default function Tree(props: Props) {
151
return () => {
152
ownerDocument.removeEventListener('keydown', handleKeyDown);
153
};
163
- }, [
164
- selectedElementID,
165
- selectChildElementInTree,
166
- selectNextElementInTree,
167
- selectParentElementInTree,
168
- selectPreviousElementInTree,
169
- store,
170
- ]);
154
+ }, [dispatch, selectedElementID, store]);
155
156
// Focus management.
157
const handleBlur = useCallback(() => setTreeFocused(false), []);
@@ -175,9 +159,12 @@ export default function Tree(props: Props) {
159
setTreeFocused(true);
160
161
if (selectedElementIndex === null && numElements > 0) {
178
- selectElementAtIndex(0);
162
+ dispatch({
163
+ type: 'SELECT_ELEMENT_AT_INDEX',
164
+ payload: 0,
165
+ });
166
}
180
- }, [numElements, selectedElementIndex, selectElementAtIndex]);
167
+ }, [dispatch, numElements, selectedElementIndex]);
168
169
const handleKeyPress = useCallback(
170
event => {
@@ -185,14 +172,14 @@ export default function Tree(props: Props) {
172
case 'Enter':
173
case ' ':
174
if (selectedElementID !== null) {
188
- selectOwner(selectedElementID);
175
+ dispatch({ type: 'SELECT_OWNER', payload: selectedElementID });
176
}
177
break;
178
default:
179
break;
180
}
181
},
195
- [selectedElementID, selectOwner]
182
+ [dispatch, selectedElementID]
183
);
184
185
const highlightElementInDOM = useCallback(
@@ -270,7 +257,6 @@ export default function Tree(props: Props) {
257
() => ({
258
baseDepth,
259
numElements,
273
- getElementAtIndex,
260
isNavigatingWithKeyboard,
261
onElementMouseEnter: handleElementMouseEnter,
262
lastScrolledIDRef,
@@ -279,7 +265,6 @@ export default function Tree(props: Props) {
265
[
266
baseDepth,
267
numElements,
282
- getElementAtIndex,
268
isNavigatingWithKeyboard,
269
handleElementMouseEnter,
270
lastScrolledIDRef,
@@ -328,7 +313,7 @@ export default function Tree(props: Props) {
313
}
314
315
function InnerElementType({ style, ...rest }) {
331
- const { ownerStack } = useContext(TreeContext);
316
+ const { ownerStack } = useContext(TreeStateContext);
317
318
// The list may need to scroll horizontally due to deeply nested elements.
319
// We don't know the maximum scroll width up front, because we're windowing.
src/devtools/views/Components/TreeContext.js
+120
-194
@@ -34,44 +34,98 @@ import Store from '../../store';
34
35
import type { Element } from './types';
36
37
-type Context = {|
37
+type StateContext = {|
38
// Tree
39
baseDepth: number,
40
numElements: number,
41
selectedElementID: number | null,
42
selectedElementIndex: number | null,
43
- getElementAtIndex(index: number): Element | null,
44
- selectChildElementInTree(): void,
45
- selectElementAtIndex(index: number): void,
46
- selectElementByID(id: number | null): void,
47
- selectNextElementInTree(): void,
48
- selectParentElementInTree(): void,
49
- selectPreviousElementInTree(): void,
43
44
// Search
45
searchIndex: number | null,
46
searchResults: Array<number>,
47
searchText: string,
55
- setSearchText(text: string): void,
56
- goToNextSearchResult(): void,
57
- goToPreviousSearchResult(): void,
48
49
// Owners
50
+ ownerFlatTree: Array<number> | null,
51
ownerStack: Array<number>,
52
ownerStackIndex: number | null,
62
- resetOwnerStack(): void,
63
- selectOwner(id: number): void,
64
-
65
- // Injected by parent HTML/JavaScript
66
- viewElementSource: Function | null,
53
54
// Inspection element panel
69
- // Updated separately so we can avoid suspending when selection changes
55
inspectedElementID: number | null,
56
|};
57
73
-const TreeContext = createContext<Context>(((null: any): Context));
74
-TreeContext.displayName = 'TreeContext';
58
+type ACTION_GO_TO_NEXT_SEARCH_RESULT = {|
59
+ type: 'GO_TO_NEXT_SEARCH_RESULT',
60
+|};
61
+type ACTION_GO_TO_PREVIOUS_SEARCH_RESULT = {|
62
+ type: 'GO_TO_PREVIOUS_SEARCH_RESULT',
63
+|};
64
+type ACTION_HANDLE_STORE_MUTATION = {|
65
+ type: 'HANDLE_STORE_MUTATION',
66
+ payload: [Uint32Array, Uint32Array],
67
+|};
68
+type ACTION_RESET_OWNER_STACK = {|
69
+ type: 'RESET_OWNER_STACK',
70
+|};
71
+type ACTION_SELECT_CHILD_ELEMENT_IN_TREE = {|
72
+ type: 'SELECT_CHILD_ELEMENT_IN_TREE',
73
+|};
74
+type ACTION_SELECT_ELEMENT_AT_INDEX = {|
75
+ type: 'SELECT_ELEMENT_AT_INDEX',
76
+ payload: number | null,
77
+|};
78
+type ACTION_SELECT_ELEMENT_BY_ID = {|
79
+ type: 'SELECT_ELEMENT_BY_ID',
80
+ payload: number | null,
81
+|};
82
+type ACTION_SELECT_NEXT_ELEMENT_IN_TREE = {|
83
+ type: 'SELECT_NEXT_ELEMENT_IN_TREE',
84
+|};
85
+type ACTION_SELECT_PARENT_ELEMENT_IN_TREE = {|
86
+ type: 'SELECT_PARENT_ELEMENT_IN_TREE',
87
+|};
88
+type ACTION_SELECT_PREVIOUS_ELEMENT_IN_TREE = {|
89
+ type: 'SELECT_PREVIOUS_ELEMENT_IN_TREE',
90
+|};
91
+type ACTION_SELECT_OWNER = {|
92
+ type: 'SELECT_OWNER',
93
+ payload: number,
94
+|};
95
+type ACTION_SET_SEARCH_TEXT = {|
96
+ type: 'SET_SEARCH_TEXT',
97
+ payload: string,
98
+|};
99
+type ACTION_UPDATE_INSPECTED_ELEMENT_ID = {|
100
+ type: 'UPDATE_INSPECTED_ELEMENT_ID',
101
+|};
102
+
103
+type Action =
104
+ | ACTION_GO_TO_NEXT_SEARCH_RESULT
105
+ | ACTION_GO_TO_PREVIOUS_SEARCH_RESULT
106
+ | ACTION_HANDLE_STORE_MUTATION
107
+ | ACTION_RESET_OWNER_STACK
108
+ | ACTION_SELECT_CHILD_ELEMENT_IN_TREE
109
+ | ACTION_SELECT_ELEMENT_AT_INDEX
110
+ | ACTION_SELECT_ELEMENT_BY_ID
111
+ | ACTION_SELECT_NEXT_ELEMENT_IN_TREE
112
+ | ACTION_SELECT_PARENT_ELEMENT_IN_TREE
113
+ | ACTION_SELECT_PREVIOUS_ELEMENT_IN_TREE
114
+ | ACTION_SELECT_OWNER
115
+ | ACTION_SET_SEARCH_TEXT
116
+ | ACTION_UPDATE_INSPECTED_ELEMENT_ID;
117
+
118
+type DispatcherContext = (action: Action) => void;
119
+
120
+const TreeStateContext = createContext<StateContext>(
121
+ ((null: any): StateContext)
122
+);
123
+TreeStateContext.displayName = 'TreeStateContext';
124
+
125
+const TreeDispatcherContext = createContext<DispatcherContext>(
126
+ ((null: any): DispatcherContext)
127
+);
128
+TreeDispatcherContext.displayName = 'TreeDispatcherContext';
129
130
type State = {|
131
// Tree
@@ -88,33 +142,13 @@ type State = {|
142
// Owners
143
ownerStack: Array<number>,
144
ownerStackIndex: number | null,
91
- _ownerFlatTree: Array<number> | null,
145
+ ownerFlatTree: Array<number> | null,
146
147
// Inspection element panel
148
inspectedElementID: number | null,
149
|};
150
97
-type Action = {|
98
- type:
99
- | 'GO_TO_NEXT_SEARCH_RESULT'
100
- | 'GO_TO_PREVIOUS_SEARCH_RESULT'
101
- | 'HANDLE_STORE_MUTATION'
102
- | 'RESET_OWNER_STACK'
103
- | 'SELECT_CHILD_ELEMENT_IN_TREE'
104
- | 'SELECT_ELEMENT_AT_INDEX'
105
- | 'SELECT_ELEMENT_BY_ID'
106
- | 'SELECT_NEXT_ELEMENT_IN_TREE'
107
- | 'SELECT_PARENT_ELEMENT_IN_TREE'
108
- | 'SELECT_PREVIOUS_ELEMENT_IN_TREE'
109
- | 'SELECT_OWNER'
110
- | 'SET_SEARCH_TEXT'
111
- | 'UPDATE_INSPECTED_ELEMENT_ID',
112
- payload?: any,
113
-|};
114
-
151
function reduceTreeState(store: Store, state: State, action: Action): State {
116
- const { type, payload } = action;
117
-
152
let {
153
numElements,
154
ownerStack,
@@ -126,7 +160,7 @@ function reduceTreeState(store: Store, state: State, action: Action): State {
160
161
// Base tree should ignore selected element changes when the owner's tree is active.
162
if (ownerStack.length === 0) {
129
- switch (type) {
163
+ switch (action.type) {
164
case 'HANDLE_STORE_MUTATION':
165
numElements = store.numElements;
166
@@ -157,18 +191,18 @@ function reduceTreeState(store: Store, state: State, action: Action): State {
191
}
192
break;
193
case 'SELECT_ELEMENT_AT_INDEX':
160
- selectedElementIndex = ((payload: any): number | null);
194
+ selectedElementIndex = (action: ACTION_SELECT_ELEMENT_AT_INDEX).payload;
195
break;
196
case 'SELECT_ELEMENT_BY_ID':
197
// Skip lookup in this case; it would be redundant.
198
// It might also cause problems if the specified element was inside of a (not yet expanded) subtree.
199
lookupIDForIndex = false;
200
167
- selectedElementID = payload;
201
+ selectedElementID = (action: ACTION_SELECT_ELEMENT_BY_ID).payload;
202
selectedElementIndex =
169
- payload === null
203
+ selectedElementID === null
204
? null
171
- : store.getIndexOfElementID(((payload: any): number));
205
+ : store.getIndexOfElementID(selectedElementID);
206
break;
207
case 'SELECT_NEXT_ELEMENT_IN_TREE':
208
if (
@@ -229,8 +263,6 @@ function reduceTreeState(store: Store, state: State, action: Action): State {
263
}
264
265
function reduceSearchState(store: Store, state: State, action: Action): State {
232
- const { type, payload } = action;
233
-
266
let {
267
ownerStack,
268
searchIndex,
@@ -252,7 +284,7 @@ function reduceSearchState(store: Store, state: State, action: Action): State {
284
285
// Search isn't supported when the owner's tree is active.
286
if (ownerStack.length === 0) {
255
- switch (type) {
287
+ switch (action.type) {
288
case 'GO_TO_NEXT_SEARCH_RESULT':
289
if (numPrevSearchResults > 0) {
290
didRequestSearch = true;
@@ -274,7 +306,7 @@ function reduceSearchState(store: Store, state: State, action: Action): State {
306
const [
307
addedElementIDs,
308
removedElementIDs,
277
- ] = ((payload: any): Array<Uint32Array>);
309
+ ] = (action: ACTION_HANDLE_STORE_MUTATION).payload;
310
311
removedElementIDs.forEach(id => {
312
// Prune this item from the search results.
@@ -336,7 +368,7 @@ function reduceSearchState(store: Store, state: State, action: Action): State {
368
case 'SET_SEARCH_TEXT':
369
searchIndex = null;
370
searchResults = [];
339
- searchText = ((payload: any): string);
371
+ searchText = (action: ACTION_SET_SEARCH_TEXT).payload;
372
373
if (searchText !== '') {
374
const regExp = createRegExp(searchText);
@@ -394,24 +426,22 @@ function reduceSearchState(store: Store, state: State, action: Action): State {
426
}
427
428
function reduceOwnersState(store: Store, state: State, action: Action): State {
397
- const { payload, type } = action;
398
-
429
let {
430
baseDepth,
431
numElements,
432
selectedElementID,
433
selectedElementIndex,
434
+ ownerFlatTree,
435
ownerStack,
436
ownerStackIndex,
437
searchIndex,
438
searchResults,
439
searchText,
409
- _ownerFlatTree,
440
} = state;
441
442
let prevSelectedElementIndex = selectedElementIndex;
443
414
- switch (type) {
444
+ switch (action.type) {
445
case 'HANDLE_STORE_MUTATION':
446
if (ownerStack.length > 0) {
447
let indexOfRemovedItem = -1;
@@ -425,15 +455,15 @@ function reduceOwnersState(store: Store, state: State, action: Action): State {
455
if (indexOfRemovedItem >= 0) {
456
ownerStack = ownerStack.slice(0, indexOfRemovedItem);
457
if (ownerStack.length === 0) {
428
- _ownerFlatTree = null;
458
+ ownerFlatTree = null;
459
ownerStackIndex = null;
460
} else {
461
ownerStackIndex = ownerStack.length - 1;
462
}
463
}
434
- if (selectedElementID !== null && _ownerFlatTree !== null) {
464
+ if (selectedElementID !== null && ownerFlatTree !== null) {
465
// Mutation might have caused the index of this ID to shift.
436
- selectedElementIndex = _ownerFlatTree.indexOf(selectedElementID);
466
+ selectedElementIndex = ownerFlatTree.indexOf(selectedElementID);
467
}
468
} else {
469
if (selectedElementID !== null) {
@@ -454,30 +484,31 @@ function reduceOwnersState(store: Store, state: State, action: Action): State {
484
selectedElementID !== null
485
? store.getIndexOfElementID(selectedElementID)
486
: null;
457
- _ownerFlatTree = null;
487
+ ownerFlatTree = null;
488
break;
489
case 'SELECT_ELEMENT_AT_INDEX':
460
- if (_ownerFlatTree !== null) {
461
- selectedElementIndex = ((payload: any): number | null);
490
+ if (ownerFlatTree !== null) {
491
+ selectedElementIndex = (action: ACTION_SELECT_ELEMENT_AT_INDEX).payload;
492
}
493
break;
494
case 'SELECT_ELEMENT_BY_ID':
465
- if (_ownerFlatTree !== null) {
495
+ if (ownerFlatTree !== null) {
496
+ const payload = (action: ACTION_SELECT_ELEMENT_BY_ID).payload;
497
selectedElementIndex =
467
- payload === null ? null : _ownerFlatTree.indexOf(payload);
498
+ payload === null ? null : ownerFlatTree.indexOf(payload);
499
}
500
break;
501
case 'SELECT_NEXT_ELEMENT_IN_TREE':
471
- if (_ownerFlatTree !== null && _ownerFlatTree.length > 0) {
502
+ if (ownerFlatTree !== null && ownerFlatTree.length > 0) {
503
if (selectedElementIndex === null) {
504
selectedElementIndex = 0;
474
- } else if (selectedElementIndex + 1 < _ownerFlatTree.length) {
505
+ } else if (selectedElementIndex + 1 < ownerFlatTree.length) {
506
selectedElementIndex++;
507
}
508
}
509
break;
510
case 'SELECT_PREVIOUS_ELEMENT_IN_TREE':
480
- if (_ownerFlatTree !== null && _ownerFlatTree.length > 0) {
511
+ if (ownerFlatTree !== null && ownerFlatTree.length > 0) {
512
if (selectedElementIndex !== null && selectedElementIndex > 0) {
513
selectedElementIndex--;
514
}
@@ -487,7 +518,8 @@ function reduceOwnersState(store: Store, state: State, action: Action): State {
518
// If the Store doesn't have any owners metadata, don't drill into an empty stack.
519
// This is a confusing user experience.
520
if (store.hasOwnerMetadata) {
490
- ownerStackIndex = ownerStack.indexOf(payload);
521
+ const id = (action: ACTION_SELECT_OWNER).payload;
522
+ ownerStackIndex = ownerStack.indexOf(id);
523
524
// Always force reset selection to be the top of the new owner tree.
525
selectedElementIndex = 0;
@@ -498,7 +530,7 @@ function reduceOwnersState(store: Store, state: State, action: Action): State {
530
if (ownerStackIndex < 0) {
531
// Add this new owner, and fill in the owners above it as well.
532
ownerStack = [];
501
- let currentOwnerID = ((payload: any): number);
533
+ let currentOwnerID = id;
534
while (currentOwnerID !== 0) {
535
ownerStack.unshift(currentOwnerID);
536
currentOwnerID = ((store.getElementByID(
@@ -524,23 +556,23 @@ function reduceOwnersState(store: Store, state: State, action: Action): State {
556
if (
557
ownerStackIndex !== state.ownerStackIndex ||
558
ownerStack !== state.ownerStack ||
527
- type === 'HANDLE_STORE_MUTATION'
559
+ action.type === 'HANDLE_STORE_MUTATION'
560
) {
561
if (ownerStackIndex === null) {
530
- _ownerFlatTree = null;
562
+ ownerFlatTree = null;
563
baseDepth = 0;
564
numElements = store.numElements;
565
} else {
534
- _ownerFlatTree = calculateCurrentOwnerList(
566
+ ownerFlatTree = calculateCurrentOwnerList(
567
store,
568
ownerStack[ownerStackIndex],
569
ownerStack[ownerStackIndex],
570
[]
571
);
572
541
- baseDepth = ((store.getElementByID(_ownerFlatTree[0]): any): Element)
573
+ baseDepth = ((store.getElementByID(ownerFlatTree[0]): any): Element)
574
.depth;
543
- numElements = _ownerFlatTree.length;
575
+ numElements = ownerFlatTree.length;
576
}
577
}
578
@@ -548,8 +580,8 @@ function reduceOwnersState(store: Store, state: State, action: Action): State {
580
if (selectedElementIndex !== prevSelectedElementIndex) {
581
if (selectedElementIndex === null) {
582
selectedElementID = null;
551
- } else if (_ownerFlatTree !== null) {
552
- selectedElementID = _ownerFlatTree[((selectedElementIndex: any): number)];
583
+ } else if (ownerFlatTree !== null) {
584
+ selectedElementID = ownerFlatTree[((selectedElementIndex: any): number)];
585
}
586
}
587
@@ -567,7 +599,7 @@ function reduceOwnersState(store: Store, state: State, action: Action): State {
599
600
ownerStack,
601
ownerStackIndex,
570
- _ownerFlatTree,
602
+ ownerFlatTree,
603
};
604
}
605
@@ -589,13 +621,10 @@ function reduceSuspenseState(
621
}
622
}
623
592
-type Props = {|
593
- children: React$Node,
594
- viewElementSource: Function | null,
595
-|};
624
+type Props = {| children: React$Node |};
625
626
// TODO Remove TreeContextController wrapper element once global ConsearchText.write API exists.
598
-function TreeContextController({ children, viewElementSource }: Props) {
627
+function TreeContextController({ children }: Props) {
628
const bridge = useContext(BridgeContext);
629
const store = useContext(StoreContext);
630
@@ -662,129 +691,20 @@ function TreeContextController({ children, viewElementSource }: Props) {
691
// Owners
692
ownerStack: [],
693
ownerStackIndex: null,
665
- _ownerFlatTree: null,
694
+ ownerFlatTree: null,
695
696
// Inspection element panel
697
inspectedElementID: null,
698
});
699
700
const dispatchWrapper = useCallback(
672
- params => {
673
- dispatch(params);
701
+ (action: Action) => {
702
+ dispatch(action);
703
next(() => dispatch({ type: 'UPDATE_INSPECTED_ELEMENT_ID' }));
704
},
705
[dispatch]
706
);
707
679
- const getElementAtIndex = useCallback(
680
- (index: number) => {
681
- return state._ownerFlatTree === null
682
- ? store.getElementAtIndex(index)
683
- : store.getElementByID(state._ownerFlatTree[index]);
684
- },
685
- [state, store]
686
- );
687
- const selectElementAtIndex = useCallback(
688
- (index: number) =>
689
- dispatchWrapper({ type: 'SELECT_ELEMENT_AT_INDEX', payload: index }),
690
- [dispatchWrapper]
691
- );
692
- const selectElementByID = useCallback(
693
- (id: number | null) =>
694
- dispatchWrapper({ type: 'SELECT_ELEMENT_BY_ID', payload: id }),
695
- [dispatchWrapper]
696
- );
697
- const setSearchText = useCallback(
698
- (text: string) =>
699
- dispatchWrapper({ type: 'SET_SEARCH_TEXT', payload: text }),
700
- [dispatchWrapper]
701
- );
702
- const goToNextSearchResult = useCallback(
703
- () => dispatchWrapper({ type: 'GO_TO_NEXT_SEARCH_RESULT' }),
704
- [dispatchWrapper]
705
- );
706
- const goToPreviousSearchResult = useCallback(
707
- () => dispatchWrapper({ type: 'GO_TO_PREVIOUS_SEARCH_RESULT' }),
708
- [dispatchWrapper]
709
- );
710
- const resetOwnerStack = useCallback(
711
- () => dispatchWrapper({ type: 'RESET_OWNER_STACK' }),
712
- [dispatchWrapper]
713
- );
714
- const selectChildElementInTree = useCallback(
715
- () => dispatchWrapper({ type: 'SELECT_CHILD_ELEMENT_IN_TREE' }),
716
- [dispatchWrapper]
717
- );
718
- const selectNextElementInTree = useCallback(
719
- () => dispatchWrapper({ type: 'SELECT_NEXT_ELEMENT_IN_TREE' }),
720
- [dispatchWrapper]
721
- );
722
- const selectParentElementInTree = useCallback(
723
- () => dispatchWrapper({ type: 'SELECT_PARENT_ELEMENT_IN_TREE' }),
724
- [dispatchWrapper]
725
- );
726
- const selectPreviousElementInTree = useCallback(
727
- () => dispatchWrapper({ type: 'SELECT_PREVIOUS_ELEMENT_IN_TREE' }),
728
- [dispatchWrapper]
729
- );
730
- const selectOwner = useCallback(
731
- (id: number) => dispatchWrapper({ type: 'SELECT_OWNER', payload: id }),
732
- [dispatchWrapper]
733
- );
734
-
735
- const value = useMemo(
736
- () => ({
737
- // Tree (derived from Store or owners state)
738
- baseDepth: state.baseDepth,
739
- numElements: state.numElements,
740
- selectedElementID: state.selectedElementID,
741
- selectedElementIndex: state.selectedElementIndex,
742
- getElementAtIndex,
743
- selectChildElementInTree,
744
- selectElementByID,
745
- selectElementAtIndex,
746
- selectNextElementInTree,
747
- selectParentElementInTree,
748
- selectPreviousElementInTree,
749
-
750
- // Search
751
- searchIndex: state.searchIndex,
752
- searchResults: state.searchResults,
753
- searchText: state.searchText,
754
- setSearchText,
755
- goToNextSearchResult,
756
- goToPreviousSearchResult,
757
-
758
- // Owners
759
- ownerStack: state.ownerStack,
760
- ownerStackIndex: state.ownerStackIndex,
761
- resetOwnerStack,
762
- selectOwner,
763
-
764
- // Inspection element panel
765
- inspectedElementID: state.inspectedElementID,
766
-
767
- // Injected by parent HTML/JavaScript
768
- viewElementSource,
769
- }),
770
- [
771
- getElementAtIndex,
772
- goToNextSearchResult,
773
- goToPreviousSearchResult,
774
- resetOwnerStack,
775
- selectChildElementInTree,
776
- selectElementAtIndex,
777
- selectElementByID,
778
- selectNextElementInTree,
779
- selectParentElementInTree,
780
- selectOwner,
781
- selectPreviousElementInTree,
782
- setSearchText,
783
- state,
784
- viewElementSource,
785
- ]
786
- );
787
-
708
// Listen for host element selections.
709
useEffect(() => {
710
const handleSelectFiber = (id: number) =>
@@ -837,7 +757,13 @@ function TreeContextController({ children, viewElementSource }: Props) {
757
return () => store.removeListener('mutated', handleStoreMutated);
758
}, [dispatchWrapper, initialRevision, store]);
759
840
- return <TreeContext.Provider value={value}>{children}</TreeContext.Provider>;
760
+ return (
761
+ <TreeStateContext.Provider value={state}>
762
+ <TreeDispatcherContext.Provider value={dispatchWrapper}>
763
+ {children}
764
+ </TreeDispatcherContext.Provider>
765
+ </TreeStateContext.Provider>
766
+ );
767
}
768
769
function calculateCurrentOwnerList(
@@ -886,4 +812,4 @@ function recursivelySearchTree(
812
);
813
}
814
889
-export { TreeContext, TreeContextController };
815
+export { TreeDispatcherContext, TreeStateContext, TreeContextController };
src/devtools/views/Components/ViewElementSourceContext.js
new
+8
@@ -0,0 +1,8 @@
1
+// @flow
2
+
3
+import { createContext } from 'react';
4
+
5
+const ViewElementSourceContext = createContext<Function | null>(null);
6
+ViewElementSourceContext.displayName = 'ViewElementSourceContext';
7
+
8
+export default ViewElementSourceContext;
src/devtools/views/DevTools.js
+47
-38
@@ -14,6 +14,7 @@ import Settings from './Settings/Settings';
14
import TabBar from './TabBar';
15
import { SettingsContextController } from './Settings/SettingsContext';
16
import { TreeContextController } from './Components/TreeContext';
17
+import ViewElementSourceContext from './Components/ViewElementSourceContext';
18
import { ProfilerContextController } from './Profiler/ProfilerContext';
19
import ReactLogo from './ReactLogo';
20
@@ -121,47 +122,55 @@ export default function DevTools({
122
profilerPortalContainer={profilerPortalContainer}
123
settingsPortalContainer={settingsPortalContainer}
124
>
124
- <TreeContextController viewElementSource={viewElementSource}>
125
- <ProfilerContextController>
126
- <div className={styles.DevTools}>
127
- {showTabBar && (
128
- <div className={styles.TabBar}>
129
- <ReactLogo />
130
- <span className={styles.DevToolsVersion}>
131
- {process.env.DEVTOOLS_VERSION}
132
- </span>
133
- <div className={styles.Spacer} />
134
- <TabBar
135
- currentTab={tab}
136
- id="DevTools"
137
- selectTab={setTab}
138
- size="large"
139
- tabs={
140
- supportsProfiling
141
- ? tabsWithProfiler
142
- : tabsWithoutProfiler
143
- }
125
+ <ViewElementSourceContext.Provider value={viewElementSource}>
126
+ <TreeContextController>
127
+ <ProfilerContextController>
128
+ <div className={styles.DevTools}>
129
+ {showTabBar && (
130
+ <div className={styles.TabBar}>
131
+ <ReactLogo />
132
+ <span className={styles.DevToolsVersion}>
133
+ {process.env.DEVTOOLS_VERSION}
134
+ </span>
135
+ <div className={styles.Spacer} />
136
+ <TabBar
137
+ currentTab={tab}
138
+ id="DevTools"
139
+ selectTab={setTab}
140
+ size="large"
141
+ tabs={
142
+ supportsProfiling
143
+ ? tabsWithProfiler
144
+ : tabsWithoutProfiler
145
+ }
146
+ />
147
+ </div>
148
+ )}
149
+ <div
150
+ className={styles.TabContent}
151
+ hidden={tab !== 'components'}
152
+ >
153
+ <Components portalContainer={componentsPortalContainer} />
154
+ </div>
155
+ <div
156
+ className={styles.TabContent}
157
+ hidden={tab !== 'profiler'}
158
+ >
159
+ <Profiler
160
+ portalContainer={profilerPortalContainer}
161
+ supportsProfiling={supportsProfiling}
162
/>
163
</div>
146
- )}
147
- <div
148
- className={styles.TabContent}
149
- hidden={tab !== 'components'}
150
- >
151
- <Components portalContainer={componentsPortalContainer} />
152
- </div>
153
- <div className={styles.TabContent} hidden={tab !== 'profiler'}>
154
- <Profiler
155
- portalContainer={profilerPortalContainer}
156
- supportsProfiling={supportsProfiling}
157
- />
158
- </div>
159
- <div className={styles.TabContent} hidden={tab !== 'settings'}>
160
- <Settings portalContainer={settingsPortalContainer} />
164
+ <div
165
+ className={styles.TabContent}
166
+ hidden={tab !== 'settings'}
167
+ >
168
+ <Settings portalContainer={settingsPortalContainer} />
169
+ </div>
170
</div>
162
- </div>
163
- </ProfilerContextController>
164
- </TreeContextController>
171
+ </ProfilerContextController>
172
+ </TreeContextController>
173
+ </ViewElementSourceContext.Provider>
174
</SettingsContextController>
175
</StoreContext.Provider>
176
</BridgeContext.Provider>
src/devtools/views/Profiler/ProfilerContext.js
+11
-4
@@ -9,7 +9,10 @@ import React, {
9
} from 'react';
10
import { unstable_batchedUpdates as batchedUpdates } from 'react-dom';
11
import { useLocalStorage, useSubscription } from '../hooks';
12
-import { TreeContext } from '../Components/TreeContext';
12
+import {
13
+ TreeDispatcherContext,
14
+ TreeStateContext,
15
+} from '../Components/TreeContext';
16
import { StoreContext } from '../context';
17
import Store from '../../store';
18
@@ -79,7 +82,8 @@ type Props = {|
82
83
function ProfilerContextController({ children }: Props) {
84
const store = useContext(StoreContext);
82
- const { selectElementByID, selectedElementID } = useContext(TreeContext);
85
+ const { selectedElementID } = useContext(TreeStateContext);
86
+ const dispatch = useContext(TreeDispatcherContext);
87
88
const subscription = useMemo(
89
() => ({
@@ -155,11 +159,14 @@ function ProfilerContextController({ children }: Props) {
159
// If this element is still in the store, then select it in the Components tab as well.
160
const element = store.getElementByID(id);
161
if (element !== null) {
158
- selectElementByID(id);
162
+ dispatch({
163
+ type: 'SELECT_ELEMENT_BY_ID',
164
+ payload: id,
165
+ });
166
}
167
}
168
},
162
- [selectElementByID, selectFiberID, selectFiberName, store]
169
+ [dispatch, selectFiberID, selectFiberName, store]
170
);
171
172
if (isProfiling) {