Split demo app into multiple roots and fixed a multi-root bug

This commit is contained in:
Brian Vaughn
2019-02-13 11:22:22 -08:00
parent 4ea81337f3
commit 79f57532b9
8 changed files with 106 additions and 65 deletions
-7
View File
@@ -1,7 +0,0 @@
.App {
/* GitHub.com frontend fonts */
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial,
sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol;
font-size: 14px;
line-height: 1.5;
}
-17
View File
@@ -1,17 +0,0 @@
// @flow
import React from 'react';
import ElementTypes from './ElementTypes';
import InspectableElements from './InspectableElements';
import List from './ToDoList';
import styles from './App.css';
export default function App() {
return (
<div className={styles.App}>
<List />
<ElementTypes />
<InspectableElements />
</div>
);
}
@@ -1,3 +1,11 @@
.App {
/* GitHub.com frontend fonts */
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial,
sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol;
font-size: 14px;
line-height: 1.5;
}
.Header {
font-size: 1.5rem;
font-weight: bold;
@@ -1,6 +1,6 @@
// @flow
import React, { Fragment } from 'react';
import React from 'react';
import Contexts from './Contexts';
import CustomHooks from './CustomHooks';
import NestedProps from './NestedProps';
@@ -10,11 +10,11 @@ import styles from './InspectableElements.css';
export default function InspectableElements() {
return (
<Fragment>
<div className={styles.App}>
<div className={styles.Header}>Inspectable elements</div>
<NestedProps />
<Contexts />
<CustomHooks />
</Fragment>
</div>
);
}
+8
View File
@@ -1,3 +1,11 @@
.App {
/* GitHub.com frontend fonts */
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial,
sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol;
font-size: 14px;
line-height: 1.5;
}
.Header {
font-size: 1.5rem;
font-weight: bold;
+3 -3
View File
@@ -1,6 +1,6 @@
// @flow
import React, { Fragment, useCallback, useState } from 'react';
import React, { useCallback, useState } from 'react';
import ListItem from './ListItem';
import styles from './List.css';
@@ -77,7 +77,7 @@ export default function List(props: Props) {
);
return (
<Fragment>
<div className={styles.App}>
<div className={styles.Header}>List</div>
<input
type="text"
@@ -106,6 +106,6 @@ export default function List(props: Props) {
/>
))}
</ul>
</Fragment>
</div>
);
}
+19 -5
View File
@@ -1,19 +1,33 @@
/** @flow */
// This test harness mounts each test app as a separate root to test multi-root applications.
import { createElement } from 'react';
import { render, unmountComponentAtNode } from 'react-dom';
import App from './App';
import ElementTypes from './ElementTypes';
import InspectableElements from './InspectableElements';
import ToDoList from './ToDoList';
const container = document.createElement('div');
const containers = [];
((document.body: any): HTMLBodyElement).appendChild(container);
function mountHelper(App) {
const container = document.createElement('div');
((document.body: any): HTMLBodyElement).appendChild(container);
containers.push(container);
function mountTestApp() {
render(createElement(App), container);
}
function mountTestApp() {
mountHelper(ToDoList);
mountHelper(InspectableElements);
mountHelper(ElementTypes);
}
function unmountTestApp() {
unmountComponentAtNode(container);
containers.forEach(container => unmountComponentAtNode(container));
}
mountTestApp();
+65 -30
View File
@@ -53,6 +53,7 @@ export default class Store extends EventEmitter {
debug('constructor', 'subscribing to Bridge');
bridge.on('operations', this.onBridgeOperations);
bridge.on('shutdown', this.onBridgeShutdown);
}
get numElements(): number {
@@ -85,7 +86,9 @@ export default class Store extends EventEmitter {
for (let i = 0; i < this._roots.length; i++) {
rootID = this._roots[i];
root = ((this._idToElement.get(rootID): any): Element);
if (rootWeight + root.weight > index) {
if (root.children.length === 0) {
continue;
} else if (rootWeight + root.weight > index) {
break;
} else {
rootWeight += root.weight;
@@ -96,7 +99,7 @@ export default class Store extends EventEmitter {
// Skip over the root itself, because roots aren't visible in the Elements tree.
const firstChildID = ((root: any): Element).children[0];
let currentElement = ((this._idToElement.get(firstChildID): any): Element);
let currentWeight = 0;
let currentWeight = rootWeight;
while (index !== currentWeight) {
for (let i = 0; i < currentElement.children.length; i++) {
const childID = currentElement.children[i];
@@ -234,22 +237,28 @@ export default class Store extends EventEmitter {
if (parentID === 0) {
debug('Add', `new root fiber ${id}`);
this._roots = this._roots.concat(id);
this._rootIDToRendererID.set(id, rendererID);
if (this._idToElement.has(id)) {
// The renderer's tree walking approach sometimes mounts the same Fiber twice with Suspense and Lazy.
// For now, we avoid adding it to the tree twice by checking if it's already been mounted.
// Maybe in the future we'll revisit this.
} else {
this._roots = this._roots.concat(id);
this._rootIDToRendererID.set(id, rendererID);
this._idToElement.set(id, {
children: [],
depth: -1,
displayName: null,
id,
key: null,
ownerID: 0,
parentID: 0,
type,
weight: 0,
});
this._idToElement.set(id, {
children: [],
depth: -1,
displayName: null,
id,
key: null,
ownerID: 0,
parentID: 0,
type,
weight: 0,
});
haveRootsChanged = true;
haveRootsChanged = true;
}
} else {
ownerID = ((operations[i]: any): number);
i++;
@@ -313,8 +322,6 @@ export default class Store extends EventEmitter {
i = i + 2;
debug('Remove', `fiber ${id} from tree`);
element = ((this._idToElement.get(id): any): Element);
parentID = element.parentID;
@@ -324,9 +331,13 @@ export default class Store extends EventEmitter {
parentElement = ((this._idToElement.get(parentID): any): Element);
if (parentElement == null) {
debug('Remove', `fiber ${id} root`);
this._roots = this._roots.filter(rootID => rootID !== id);
this._rootIDToRendererID.delete(id);
} else {
debug('Remove', `fiber ${id} from parent ${parentID}`);
parentElement.children = parentElement.children.filter(
childID => childID !== id
);
@@ -388,18 +399,42 @@ export default class Store extends EventEmitter {
this.emit('mutated', [addedElementIDs, removedElementIDs]);
};
onBridgeShutdown = () => {
debug('onBridgeShutdown', 'unsubscribing from Bridge');
bridge.off('operations', this.onBridgeOperations);
bridge.off('shutdown', this.onBridgeShutdown);
};
// DEBUG
__printTree = (rootID: number) => {
const printElement = (id: number) => {
const element = ((this._idToElement.get(id): any): Element);
console.log(
`${'•'.repeat(element.depth)}${element.id}:${element.displayName ||
''}${element.key ? `key:"${element.key}"` : ''} (${element.weight})`
);
element.children.forEach(printElement);
};
const root = ((this._idToElement.get(rootID): any): Element);
console.log('printing root:', rootID);
root.children.forEach(printElement);
__printTree = () => {
console.group('__printTree()');
this._roots.forEach((rootID: number) => {
const printElement = (id: number) => {
const element = ((this._idToElement.get(id): any): Element);
console.log(
`${'•'.repeat(element.depth)}${element.id}:${element.displayName ||
''}${element.key ? `key:"${element.key}"` : ''} (${element.weight})`
);
element.children.forEach(printElement);
};
const root = ((this._idToElement.get(rootID): any): Element);
console.group(`${rootID}:root (${root.weight})`);
root.children.forEach(printElement);
console.groupEnd();
});
console.group(`List of ${this.numElements} elements`);
for (let i = 0; i < this.numElements; i++) {
//if (i === 4) { debugger }
const element = this.getElementAtIndex(i);
if (element != null) {
console.log(
`${'•'.repeat(element.depth)}${i}: ${element.displayName ||
'Unknown'}`
);
}
}
console.groupEnd();
console.groupEnd();
};
}