Split demo app into multiple roots and fixed a multi-root bug
Brian Vaughn committed
Feb 13, 2019 at 11:22 UTC
79f57532b9fe117d82c2934b7e6a2b281fa27dcb
8 files changed
+108
-67
shells/dev/app/App.css
deleted
-7
@@ -1,7 +0,0 @@
1
-.App {
2
- /* GitHub.com frontend fonts */
3
- font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial,
4
- sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol;
5
- font-size: 14px;
6
- line-height: 1.5;
7
-}
shells/dev/app/App.js
deleted
-17
@@ -1,17 +0,0 @@
1
-// @flow
2
-
3
-import React from 'react';
4
-import ElementTypes from './ElementTypes';
5
-import InspectableElements from './InspectableElements';
6
-import List from './ToDoList';
7
-import styles from './App.css';
8
-
9
-export default function App() {
10
- return (
11
- <div className={styles.App}>
12
- <List />
13
- <ElementTypes />
14
- <InspectableElements />
15
- </div>
16
- );
17
-}
shells/dev/app/InspectableElements/InspectableElements.css
+8
@@ -1,3 +1,11 @@
1
+.App {
2
+ /* GitHub.com frontend fonts */
3
+ font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial,
4
+ sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol;
5
+ font-size: 14px;
6
+ line-height: 1.5;
7
+}
8
+
9
.Header {
10
font-size: 1.5rem;
11
font-weight: bold;
shells/dev/app/InspectableElements/InspectableElements.js
+3
-3
@@ -1,6 +1,6 @@
1
// @flow
2
3
-import React, { Fragment } from 'react';
3
+import React from 'react';
4
import Contexts from './Contexts';
5
import CustomHooks from './CustomHooks';
6
import NestedProps from './NestedProps';
@@ -10,11 +10,11 @@ import styles from './InspectableElements.css';
10
11
export default function InspectableElements() {
12
return (
13
- <Fragment>
13
+ <div className={styles.App}>
14
<div className={styles.Header}>Inspectable elements</div>
15
<NestedProps />
16
<Contexts />
17
<CustomHooks />
18
- </Fragment>
18
+ </div>
19
);
20
}
shells/dev/app/ToDoList/List.css
+8
@@ -1,3 +1,11 @@
1
+.App {
2
+ /* GitHub.com frontend fonts */
3
+ font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial,
4
+ sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol;
5
+ font-size: 14px;
6
+ line-height: 1.5;
7
+}
8
+
9
.Header {
10
font-size: 1.5rem;
11
font-weight: bold;
shells/dev/app/ToDoList/List.js
+3
-3
@@ -1,6 +1,6 @@
1
// @flow
2
3
-import React, { Fragment, useCallback, useState } from 'react';
3
+import React, { useCallback, useState } from 'react';
4
import ListItem from './ListItem';
5
import styles from './List.css';
6
@@ -77,7 +77,7 @@ export default function List(props: Props) {
77
);
78
79
return (
80
- <Fragment>
80
+ <div className={styles.App}>
81
<div className={styles.Header}>List</div>
82
<input
83
type="text"
@@ -106,6 +106,6 @@ export default function List(props: Props) {
106
/>
107
))}
108
</ul>
109
- </Fragment>
109
+ </div>
110
);
111
}
shells/dev/app/index.js
+19
-5
@@ -1,19 +1,33 @@
1
/** @flow */
2
3
+// This test harness mounts each test app as a separate root to test multi-root applications.
4
+
5
import { createElement } from 'react';
6
import { render, unmountComponentAtNode } from 'react-dom';
5
-import App from './App';
7
+import ElementTypes from './ElementTypes';
8
+import InspectableElements from './InspectableElements';
9
+import ToDoList from './ToDoList';
10
7
-const container = document.createElement('div');
11
+const containers = [];
12
9
-((document.body: any): HTMLBodyElement).appendChild(container);
13
+function mountHelper(App) {
14
+ const container = document.createElement('div');
15
+
16
+ ((document.body: any): HTMLBodyElement).appendChild(container);
17
+
18
+ containers.push(container);
19
11
-function mountTestApp() {
20
render(createElement(App), container);
21
}
22
23
+function mountTestApp() {
24
+ mountHelper(ToDoList);
25
+ mountHelper(InspectableElements);
26
+ mountHelper(ElementTypes);
27
+}
28
+
29
function unmountTestApp() {
16
- unmountComponentAtNode(container);
30
+ containers.forEach(container => unmountComponentAtNode(container));
31
}
32
33
mountTestApp();
src/devtools/store.js
+67
-32
@@ -53,6 +53,7 @@ export default class Store extends EventEmitter {
53
debug('constructor', 'subscribing to Bridge');
54
55
bridge.on('operations', this.onBridgeOperations);
56
+ bridge.on('shutdown', this.onBridgeShutdown);
57
}
58
59
get numElements(): number {
@@ -85,7 +86,9 @@ export default class Store extends EventEmitter {
86
for (let i = 0; i < this._roots.length; i++) {
87
rootID = this._roots[i];
88
root = ((this._idToElement.get(rootID): any): Element);
88
- if (rootWeight + root.weight > index) {
89
+ if (root.children.length === 0) {
90
+ continue;
91
+ } else if (rootWeight + root.weight > index) {
92
break;
93
} else {
94
rootWeight += root.weight;
@@ -96,7 +99,7 @@ export default class Store extends EventEmitter {
99
// Skip over the root itself, because roots aren't visible in the Elements tree.
100
const firstChildID = ((root: any): Element).children[0];
101
let currentElement = ((this._idToElement.get(firstChildID): any): Element);
99
- let currentWeight = 0;
102
+ let currentWeight = rootWeight;
103
while (index !== currentWeight) {
104
for (let i = 0; i < currentElement.children.length; i++) {
105
const childID = currentElement.children[i];
@@ -234,22 +237,28 @@ export default class Store extends EventEmitter {
237
if (parentID === 0) {
238
debug('Add', `new root fiber ${id}`);
239
237
- this._roots = this._roots.concat(id);
238
- this._rootIDToRendererID.set(id, rendererID);
239
-
240
- this._idToElement.set(id, {
241
- children: [],
242
- depth: -1,
243
- displayName: null,
244
- id,
245
- key: null,
246
- ownerID: 0,
247
- parentID: 0,
248
- type,
249
- weight: 0,
250
- });
251
-
252
- haveRootsChanged = true;
240
+ if (this._idToElement.has(id)) {
241
+ // The renderer's tree walking approach sometimes mounts the same Fiber twice with Suspense and Lazy.
242
+ // For now, we avoid adding it to the tree twice by checking if it's already been mounted.
243
+ // Maybe in the future we'll revisit this.
244
+ } else {
245
+ this._roots = this._roots.concat(id);
246
+ this._rootIDToRendererID.set(id, rendererID);
247
+
248
+ this._idToElement.set(id, {
249
+ children: [],
250
+ depth: -1,
251
+ displayName: null,
252
+ id,
253
+ key: null,
254
+ ownerID: 0,
255
+ parentID: 0,
256
+ type,
257
+ weight: 0,
258
+ });
259
+
260
+ haveRootsChanged = true;
261
+ }
262
} else {
263
ownerID = ((operations[i]: any): number);
264
i++;
@@ -313,8 +322,6 @@ export default class Store extends EventEmitter {
322
323
i = i + 2;
324
316
- debug('Remove', `fiber ${id} from tree`);
317
-
325
element = ((this._idToElement.get(id): any): Element);
326
parentID = element.parentID;
327
@@ -324,9 +331,13 @@ export default class Store extends EventEmitter {
331
332
parentElement = ((this._idToElement.get(parentID): any): Element);
333
if (parentElement == null) {
334
+ debug('Remove', `fiber ${id} root`);
335
+
336
this._roots = this._roots.filter(rootID => rootID !== id);
337
this._rootIDToRendererID.delete(id);
338
} else {
339
+ debug('Remove', `fiber ${id} from parent ${parentID}`);
340
+
341
parentElement.children = parentElement.children.filter(
342
childID => childID !== id
343
);
@@ -388,18 +399,42 @@ export default class Store extends EventEmitter {
399
this.emit('mutated', [addedElementIDs, removedElementIDs]);
400
};
401
402
+ onBridgeShutdown = () => {
403
+ debug('onBridgeShutdown', 'unsubscribing from Bridge');
404
+
405
+ bridge.off('operations', this.onBridgeOperations);
406
+ bridge.off('shutdown', this.onBridgeShutdown);
407
+ };
408
+
409
// DEBUG
392
- __printTree = (rootID: number) => {
393
- const printElement = (id: number) => {
394
- const element = ((this._idToElement.get(id): any): Element);
395
- console.log(
396
- `${'•'.repeat(element.depth)}${element.id}:${element.displayName ||
397
- ''}${element.key ? `key:"${element.key}"` : ''} (${element.weight})`
398
- );
399
- element.children.forEach(printElement);
400
- };
401
- const root = ((this._idToElement.get(rootID): any): Element);
402
- console.log('printing root:', rootID);
403
- root.children.forEach(printElement);
410
+ __printTree = () => {
411
+ console.group('__printTree()');
412
+ this._roots.forEach((rootID: number) => {
413
+ const printElement = (id: number) => {
414
+ const element = ((this._idToElement.get(id): any): Element);
415
+ console.log(
416
+ `${'•'.repeat(element.depth)}${element.id}:${element.displayName ||
417
+ ''}${element.key ? `key:"${element.key}"` : ''} (${element.weight})`
418
+ );
419
+ element.children.forEach(printElement);
420
+ };
421
+ const root = ((this._idToElement.get(rootID): any): Element);
422
+ console.group(`${rootID}:root (${root.weight})`);
423
+ root.children.forEach(printElement);
424
+ console.groupEnd();
425
+ });
426
+ console.group(`List of ${this.numElements} elements`);
427
+ for (let i = 0; i < this.numElements; i++) {
428
+ //if (i === 4) { debugger }
429
+ const element = this.getElementAtIndex(i);
430
+ if (element != null) {
431
+ console.log(
432
+ `${'•'.repeat(element.depth)}${i}: ${element.displayName ||
433
+ 'Unknown'}`
434
+ );
435
+ }
436
+ }
437
+ console.groupEnd();
438
+ console.groupEnd();
439
};
440
}