mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Refactored owners list to use cached metadata (and added more tests)
This commit is contained in:
@@ -1,20 +1,121 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Store owners list should drill through interleaved intermediate components: 1: mount 1`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
▾ <Intermediate key="intermediate">
|
||||
<Leaf key="leaf">
|
||||
▾ <Wrapper key="wrapper">
|
||||
<Leaf>
|
||||
<Leaf key="leaf">
|
||||
`;
|
||||
|
||||
exports[`Store owners list should drill through interleaved intermediate components: 2: components owned by <Root> 1`] = `
|
||||
" ▾ <Root>
|
||||
▾ <Intermediate key=\\"intermediate\\">
|
||||
<Leaf>
|
||||
<Leaf key=\\"leaf\\">"
|
||||
`;
|
||||
|
||||
exports[`Store owners list should drill through interleaved intermediate components: 3: components owned by <Intermediate> 1`] = `
|
||||
" ▾ <Intermediate key=\\"intermediate\\">
|
||||
<Leaf key=\\"leaf\\">
|
||||
▾ <Wrapper key=\\"wrapper\\">"
|
||||
`;
|
||||
|
||||
exports[`Store owners list should drill through intermediate components: 1: mount 1`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
▾ <Intermediate>
|
||||
▾ <Wrapper>
|
||||
<Leaf key="children">
|
||||
<Leaf>
|
||||
`;
|
||||
|
||||
exports[`Store owners list should drill through intermediate components: 2: components owned by <Root> 1`] = `
|
||||
" ▾ <Root>
|
||||
▾ <Intermediate>
|
||||
<Leaf key=\\"children\\">"
|
||||
<Leaf>"
|
||||
`;
|
||||
|
||||
exports[`Store owners list should drill through intermediate components: 3: components owned by <Intermediate> 1`] = `
|
||||
" ▾ <Intermediate>
|
||||
▾ <Wrapper>"
|
||||
`;
|
||||
|
||||
exports[`Store owners list should show the proper owners list order and contents after insertions and deletions: 1: mount 1`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
▾ <Intermediate>
|
||||
▾ <Wrapper>
|
||||
<Leaf>
|
||||
`;
|
||||
|
||||
exports[`Store owners list should show the proper owners list order and contents after insertions and deletions: 2: components owned by <Root> 1`] = `
|
||||
" ▾ <Root>
|
||||
▾ <Intermediate>
|
||||
<Leaf>"
|
||||
`;
|
||||
|
||||
exports[`Store owners list should show the proper owners list order and contents after insertions and deletions: 3: update to add direct 1`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<Leaf>
|
||||
▾ <Intermediate>
|
||||
▾ <Wrapper>
|
||||
<Leaf>
|
||||
`;
|
||||
|
||||
exports[`Store owners list should show the proper owners list order and contents after insertions and deletions: 4: components owned by <Root> 1`] = `
|
||||
" ▾ <Root>
|
||||
<Leaf>
|
||||
▾ <Intermediate>
|
||||
<Leaf>"
|
||||
`;
|
||||
|
||||
exports[`Store owners list should show the proper owners list order and contents after insertions and deletions: 5: update to remove indirect 1`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<Leaf>
|
||||
`;
|
||||
|
||||
exports[`Store owners list should show the proper owners list order and contents after insertions and deletions: 6: components owned by <Root> 1`] = `
|
||||
" ▾ <Root>
|
||||
<Leaf>"
|
||||
`;
|
||||
|
||||
exports[`Store owners list should show the proper owners list order and contents after insertions and deletions: 7: update to remove both 1`] = `
|
||||
[root]
|
||||
<Root>
|
||||
`;
|
||||
|
||||
exports[`Store owners list should show the proper owners list order and contents after insertions and deletions: 8: components owned by <Root> 1`] = `" <Root>"`;
|
||||
|
||||
exports[`Store owners list should show the proper owners list ordering after reordered children: 1: mount (ascending) 1`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<Leaf key="A">
|
||||
<Leaf key="B">
|
||||
<Leaf key="C">
|
||||
`;
|
||||
|
||||
exports[`Store owners list should show the proper owners list ordering after reordered children: 2: components owned by <Root> 1`] = `
|
||||
" ▾ <Root>
|
||||
<Leaf key=\\"A\\">
|
||||
<Leaf key=\\"B\\">
|
||||
<Leaf key=\\"C\\">"
|
||||
`;
|
||||
|
||||
exports[`Store owners list should show the proper owners list ordering after reordered children: 3: update (descending) 1`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<Leaf key="C">
|
||||
<Leaf key="B">
|
||||
<Leaf key="A">
|
||||
`;
|
||||
|
||||
exports[`Store owners list should show the proper owners list ordering after reordered children: 4: components owned by <Root> 1`] = `
|
||||
" ▾ <Root>
|
||||
<Leaf key=\\"C\\">
|
||||
<Leaf key=\\"B\\">
|
||||
<Leaf key=\\"A\\">"
|
||||
`;
|
||||
|
||||
@@ -27,7 +27,9 @@ describe('Store owners list', () => {
|
||||
it('should drill through intermediate components', () => {
|
||||
const Root = () => (
|
||||
<Intermediate>
|
||||
<Leaf key="children" />
|
||||
<div>
|
||||
<Leaf />
|
||||
</div>
|
||||
</Intermediate>
|
||||
);
|
||||
const Wrapper = ({ children }) => children;
|
||||
@@ -47,4 +49,123 @@ describe('Store owners list', () => {
|
||||
printOwnersList(store.getOwnersListForElement(intermediateID))
|
||||
).toMatchSnapshot('3: components owned by <Intermediate>');
|
||||
});
|
||||
|
||||
it('should drill through interleaved intermediate components', () => {
|
||||
const Root = () => [
|
||||
<Intermediate key="intermediate">
|
||||
<Leaf />
|
||||
</Intermediate>,
|
||||
<Leaf key="leaf" />,
|
||||
];
|
||||
const Wrapper = ({ children }) => children;
|
||||
const Leaf = () => <div>Leaf</div>;
|
||||
const Intermediate = ({ children }) => [
|
||||
<Leaf key="leaf" />,
|
||||
<Wrapper key="wrapper">{children}</Wrapper>,
|
||||
];
|
||||
|
||||
act(() => ReactDOM.render(<Root />, document.createElement('div')));
|
||||
expect(store).toMatchSnapshot('1: mount');
|
||||
|
||||
const rootID = store.getElementIDAtIndex(0);
|
||||
expect(
|
||||
printOwnersList(store.getOwnersListForElement(rootID))
|
||||
).toMatchSnapshot('2: components owned by <Root>');
|
||||
|
||||
const intermediateID = store.getElementIDAtIndex(1);
|
||||
expect(
|
||||
printOwnersList(store.getOwnersListForElement(intermediateID))
|
||||
).toMatchSnapshot('3: components owned by <Intermediate>');
|
||||
});
|
||||
|
||||
it('should show the proper owners list order and contents after insertions and deletions', () => {
|
||||
const Root = ({ includeDirect, includeIndirect }) => (
|
||||
<div>
|
||||
{includeDirect ? <Leaf /> : null}
|
||||
{includeIndirect ? (
|
||||
<Intermediate>
|
||||
<Leaf />
|
||||
</Intermediate>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
const Wrapper = ({ children }) => children;
|
||||
const Leaf = () => <div>Leaf</div>;
|
||||
const Intermediate = ({ children }) => <Wrapper>{children}</Wrapper>;
|
||||
|
||||
const container = document.createElement('div');
|
||||
|
||||
act(() =>
|
||||
ReactDOM.render(
|
||||
<Root includeDirect={false} includeIndirect={true} />,
|
||||
container
|
||||
)
|
||||
);
|
||||
expect(store).toMatchSnapshot('1: mount');
|
||||
|
||||
const rootID = store.getElementIDAtIndex(0);
|
||||
expect(
|
||||
printOwnersList(store.getOwnersListForElement(rootID))
|
||||
).toMatchSnapshot('2: components owned by <Root>');
|
||||
|
||||
act(() =>
|
||||
ReactDOM.render(
|
||||
<Root includeDirect={true} includeIndirect={true} />,
|
||||
container
|
||||
)
|
||||
);
|
||||
expect(store).toMatchSnapshot('3: update to add direct');
|
||||
|
||||
expect(
|
||||
printOwnersList(store.getOwnersListForElement(rootID))
|
||||
).toMatchSnapshot('4: components owned by <Root>');
|
||||
|
||||
act(() =>
|
||||
ReactDOM.render(
|
||||
<Root includeDirect={true} includeIndirect={false} />,
|
||||
container
|
||||
)
|
||||
);
|
||||
expect(store).toMatchSnapshot('5: update to remove indirect');
|
||||
|
||||
expect(
|
||||
printOwnersList(store.getOwnersListForElement(rootID))
|
||||
).toMatchSnapshot('6: components owned by <Root>');
|
||||
|
||||
act(() =>
|
||||
ReactDOM.render(
|
||||
<Root includeDirect={false} includeIndirect={false} />,
|
||||
container
|
||||
)
|
||||
);
|
||||
expect(store).toMatchSnapshot('7: update to remove both');
|
||||
|
||||
expect(
|
||||
printOwnersList(store.getOwnersListForElement(rootID))
|
||||
).toMatchSnapshot('8: components owned by <Root>');
|
||||
});
|
||||
|
||||
it('should show the proper owners list ordering after reordered children', () => {
|
||||
const Root = ({ ascending }) =>
|
||||
ascending
|
||||
? [<Leaf key="A" />, <Leaf key="B" />, <Leaf key="C" />]
|
||||
: [<Leaf key="C" />, <Leaf key="B" />, <Leaf key="A" />];
|
||||
const Leaf = () => <div>Leaf</div>;
|
||||
|
||||
const container = document.createElement('div');
|
||||
act(() => ReactDOM.render(<Root ascending={true} />, container));
|
||||
expect(store).toMatchSnapshot('1: mount (ascending)');
|
||||
|
||||
const rootID = store.getElementIDAtIndex(0);
|
||||
expect(
|
||||
printOwnersList(store.getOwnersListForElement(rootID))
|
||||
).toMatchSnapshot('2: components owned by <Root>');
|
||||
|
||||
act(() => ReactDOM.render(<Root ascending={false} />, container));
|
||||
expect(store).toMatchSnapshot('3: update (descending)');
|
||||
|
||||
expect(
|
||||
printOwnersList(store.getOwnersListForElement(rootID))
|
||||
).toMatchSnapshot('4: components owned by <Root>');
|
||||
});
|
||||
});
|
||||
|
||||
+81
-31
@@ -81,6 +81,10 @@ export default class Store extends EventEmitter {
|
||||
// When profiling is in progress, operations are stored so that we can later reconstruct past commit trees.
|
||||
_isProfiling: boolean = false;
|
||||
|
||||
// Map of element (id) to the set of elements (ids) it owns.
|
||||
// This map enables getOwnersListForElement() to avoid traversing the entire tree.
|
||||
_ownersMap: Map<number, Set<number>> = new Map();
|
||||
|
||||
// Suspense cache for reading profiling data.
|
||||
_profilingCache: ProfilingCache;
|
||||
|
||||
@@ -406,10 +410,65 @@ export default class Store extends EventEmitter {
|
||||
return index;
|
||||
}
|
||||
|
||||
getOwnersListForElement(id: number): Array<Element> {
|
||||
getOwnersListForElement(ownerID: number): Array<Element> {
|
||||
const list = [];
|
||||
let element = this._idToElement.get(ownerID);
|
||||
if (element != null) {
|
||||
list.push({
|
||||
...element,
|
||||
depth: 0,
|
||||
});
|
||||
|
||||
this._populateOwnersList(id, id, 0, list);
|
||||
const unsortedIDs = this._ownersMap.get(ownerID);
|
||||
if (unsortedIDs !== undefined) {
|
||||
const depthMap: Map<number, number> = new Map([[ownerID, 0]]);
|
||||
|
||||
// Items in a set are ordered based on insertion.
|
||||
// This does not correlate with their order in the tree.
|
||||
// So first we need to order them.
|
||||
// I wish we could avoid this sorting operation; we could sort at insertion time,
|
||||
// but then we'd have to pay sorting costs even if the owners list was never used.
|
||||
// Seems better to defer the cost, since the set of ids is probably pretty small.
|
||||
const sortedIDs = Array.from(unsortedIDs).sort(
|
||||
(idA, idB) =>
|
||||
((this.getIndexOfElementID(idA): any): number) -
|
||||
((this.getIndexOfElementID(idB): any): number)
|
||||
);
|
||||
|
||||
// Next we need to determine the appropriate depth for each element in the list.
|
||||
// The depth in the list may not correspond to the depth in the tree,
|
||||
// because the list has been filtered to remove intermediate components.
|
||||
// Perhaps the easiest way to do this is to walk up the tree until we reach either:
|
||||
// (1) another node that's already in the tree, or (2) the root (owner)
|
||||
// at which point, our depth is just the depth of that node plus one.
|
||||
sortedIDs.forEach(id => {
|
||||
const element = this._idToElement.get(id);
|
||||
if (element != null) {
|
||||
let parentID = element.parentID;
|
||||
|
||||
let depth = 0;
|
||||
while (parentID > 0) {
|
||||
if (parentID === ownerID || unsortedIDs.has(parentID)) {
|
||||
depth = depthMap.get(parentID) + 1;
|
||||
depthMap.set(id, depth);
|
||||
break;
|
||||
}
|
||||
const parent = this._idToElement.get(parentID);
|
||||
if (parent == null) {
|
||||
break;
|
||||
}
|
||||
parentID = parent.parentID;
|
||||
}
|
||||
|
||||
if (depth === 0) {
|
||||
throw Error('Invalid owners list');
|
||||
}
|
||||
|
||||
list.push({ ...element, depth });
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
@@ -561,32 +620,6 @@ export default class Store extends EventEmitter {
|
||||
THROTTLE_CAPTURE_SCREENSHOT_DURATION
|
||||
);
|
||||
|
||||
_populateOwnersList(
|
||||
id: number,
|
||||
ownerID: number,
|
||||
depth: number,
|
||||
list: Array<Element>
|
||||
) {
|
||||
const element = this._idToElement.get(id);
|
||||
if (element != null) {
|
||||
const isInList = id === ownerID || element.ownerID === ownerID;
|
||||
if (isInList) {
|
||||
list.push({
|
||||
...element,
|
||||
depth: depth,
|
||||
});
|
||||
}
|
||||
element.children.forEach(childID =>
|
||||
this._populateOwnersList(
|
||||
childID,
|
||||
ownerID,
|
||||
isInList ? depth + 1 : depth,
|
||||
list
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
_takeProfilingSnapshotRecursive = (id: number) => {
|
||||
const element = this.getElementByID(id);
|
||||
if (element !== null) {
|
||||
@@ -779,6 +812,15 @@ export default class Store extends EventEmitter {
|
||||
this._idToElement.set(id, element);
|
||||
addedElementIDs.push(id);
|
||||
this._adjustParentTreeWeight(parentElement, 1);
|
||||
|
||||
if (ownerID > 0) {
|
||||
let set = this._ownersMap.get(ownerID);
|
||||
if (set === undefined) {
|
||||
set = new Set();
|
||||
this._ownersMap.set(ownerID, set);
|
||||
}
|
||||
set.add(id);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -798,13 +840,13 @@ export default class Store extends EventEmitter {
|
||||
i = i + 1;
|
||||
|
||||
const element = ((this._idToElement.get(id): any): Element);
|
||||
if (element.children.length > 0) {
|
||||
const { children, ownerID, parentID, weight } = element;
|
||||
if (children.length > 0) {
|
||||
throw new Error(`Node ${id} was removed before its children.`);
|
||||
}
|
||||
|
||||
this._idToElement.delete(id);
|
||||
|
||||
const parentID = element.parentID;
|
||||
let parentElement = null;
|
||||
if (parentID === 0) {
|
||||
if (__DEBUG__) {
|
||||
@@ -830,8 +872,16 @@ export default class Store extends EventEmitter {
|
||||
parentElement.children.splice(index, 1);
|
||||
}
|
||||
|
||||
this._adjustParentTreeWeight(parentElement, -element.weight);
|
||||
this._adjustParentTreeWeight(parentElement, -weight);
|
||||
removedElementIDs.set(id, parentID);
|
||||
|
||||
this._ownersMap.delete(id);
|
||||
if (ownerID > 0) {
|
||||
const set = this._ownersMap.get(ownerID);
|
||||
if (set !== undefined) {
|
||||
set.delete(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user