@samitouri / QOS-React / commits / 79f57532b9

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 }