Use a heuristic for locating roots
Dan Abramov committed
Apr 24, 2019 at 21:32 UTC
80597a23779fad309a74f79d390ccd3c631d5330
1 file changed
+81
-20
src/backend/renderer.js
+81
-20
@@ -539,11 +539,6 @@ export function attach(
539
// When a mount or update is in progress, this value tracks the root that is being operated on.
540
let currentRootID: number = -1;
541
542
- // Track the order in which roots were added.
543
- // We will use it to disambiguate roots when restoring selection between reloads.
544
- let nextRootIndex = 0;
545
- const rootInsertionOrder: Map<number, number> = new Map();
546
-
542
function getFiberID(primaryFiber: Fiber): number {
543
if (!fiberToIDMap.has(primaryFiber)) {
544
const id = getUID();
@@ -1148,7 +1143,7 @@ export function attach(
1143
// If we have not been profiling, then we can just walk the tree and build up its current state as-is.
1144
hook.getFiberRoots(rendererID).forEach(root => {
1145
currentRootID = getFiberID(getPrimaryFiber(root.current));
1151
- rootInsertionOrder.set(currentRootID, nextRootIndex++);
1146
+ setRootPseudoKey(currentRootID, root.current);
1147
1148
if (isProfiling) {
1149
// If profiling is active, store commit time and duration, and the current interactions.
@@ -1217,19 +1212,19 @@ export function attach(
1212
current.memoizedState != null && current.memoizedState.element != null;
1213
if (!wasMounted && isMounted) {
1214
// Mount a new root.
1220
- rootInsertionOrder.set(currentRootID, nextRootIndex++);
1215
+ setRootPseudoKey(currentRootID, current);
1216
mountFiberRecursively(current, null);
1217
} else if (wasMounted && isMounted) {
1218
// Update an existing root.
1219
updateFiberRecursively(current, alternate, null);
1220
} else if (wasMounted && !isMounted) {
1221
// Unmount an existing root.
1227
- rootInsertionOrder.delete(currentRootID);
1222
+ removeRootPseudoKey(currentRootID);
1223
recordUnmount(current, false);
1224
}
1225
} else {
1226
// Mount a new root.
1232
- rootInsertionOrder.set(currentRootID, nextRootIndex++);
1227
+ setRootPseudoKey(currentRootID, current);
1228
mountFiberRecursively(current, null);
1229
}
1230
@@ -2099,18 +2094,84 @@ export function attach(
2094
mightBeOnTrackedPath = mightSiblingsBeOnTrackedPath;
2095
}
2096
2102
- function getPathFrame(fiber: Fiber): PathFrame {
2103
- const { displayName, key } = getDataForFiber(fiber);
2104
- let index = fiber.index;
2105
- if (fiber.tag === HostRoot) {
2106
- // Roots don't have a real index.
2107
- // Instead, we'll use the order in which it mounted.
2108
- const id = getFiberID(getPrimaryFiber(fiber));
2109
- const order = rootInsertionOrder.get(id);
2110
- if (typeof order !== 'number') {
2111
- throw new Error('Expected mounted root to have known insertion order.');
2097
+ // Roots don't have a real persistent identity.
2098
+ // A root's "pseudo key" is "childDisplayName:indexWithThatName".
2099
+ // For example, "App:0" or, in case of similar roots, "Story:0", "Story:1", etc.
2100
+ // We will use this to try to disambiguate roots when restoring selection between reloads.
2101
+ const rootPseudoKeys: Map<number, string> = new Map();
2102
+ const rootDisplayNameCounter: Map<string, number> = new Map();
2103
+
2104
+ function setRootPseudoKey(id: number, fiber: Fiber) {
2105
+ let preferredDisplayName = null;
2106
+ let fallbackDisplayName = null;
2107
+ let child = fiber.child;
2108
+ // Go at most three levels deep into direct children
2109
+ // while searching for a child that has a displayName.
2110
+ for (let i = 0; i < 3; i++) {
2111
+ if (child === null) {
2112
+ break;
2113
+ }
2114
+ const displayName = getDataForFiber(child).displayName;
2115
+ if (displayName !== null) {
2116
+ // Prefer display names that we get from user-defined components.
2117
+ // We want to avoid using e.g. 'Suspense' unless we find nothing else.
2118
+ if (typeof child.type === 'function') {
2119
+ // There's a few user-defined tags, but we'll prefer the ones
2120
+ // that are usually explicitly named (function or class components).
2121
+ preferredDisplayName = displayName;
2122
+ } else if (fallbackDisplayName === null) {
2123
+ fallbackDisplayName = displayName;
2124
+ }
2125
+ }
2126
+ if (preferredDisplayName !== null) {
2127
+ break;
2128
}
2113
- index = order;
2129
+ child = child.child;
2130
+ }
2131
+ const name = preferredDisplayName || fallbackDisplayName || 'Unknown';
2132
+ const counter = rootDisplayNameCounter.get(name) || 0;
2133
+ rootDisplayNameCounter.set(name, counter + 1);
2134
+ const pseudoKey = `${name}:${counter}`;
2135
+ rootPseudoKeys.set(id, pseudoKey);
2136
+ }
2137
+
2138
+ function removeRootPseudoKey(id: number) {
2139
+ const pseudoKey = rootPseudoKeys.get(id);
2140
+ if (pseudoKey === undefined) {
2141
+ throw new Error('Expected root pseudo key to be known.');
2142
+ }
2143
+ const name = pseudoKey.substring(0, pseudoKey.lastIndexOf(':'));
2144
+ const counter = rootDisplayNameCounter.get(name);
2145
+ if (counter === undefined) {
2146
+ throw new Error('Expected counter to be known.');
2147
+ }
2148
+ if (counter > 1) {
2149
+ rootDisplayNameCounter.set(name, counter - 1);
2150
+ } else {
2151
+ rootDisplayNameCounter.delete(name);
2152
+ }
2153
+ rootPseudoKeys.delete(id);
2154
+ }
2155
+
2156
+ function getPathFrame(fiber: Fiber): PathFrame {
2157
+ let { displayName, key } = getDataForFiber(fiber);
2158
+ const index = fiber.index;
2159
+ switch (fiber.tag) {
2160
+ case HostRoot:
2161
+ // Roots don't have a real displayName, index, or key.
2162
+ // Instead, we'll use the pseudo key (childDisplayName:indexWithThatName).
2163
+ const id = getFiberID(getPrimaryFiber(fiber));
2164
+ const pseudoKey = rootPseudoKeys.get(id);
2165
+ if (pseudoKey === undefined) {
2166
+ throw new Error('Expected mounted root to have known pseudo key.');
2167
+ }
2168
+ displayName = pseudoKey;
2169
+ break;
2170
+ case HostComponent:
2171
+ displayName = fiber.type;
2172
+ break;
2173
+ default:
2174
+ break;
2175
}
2176
return {
2177
displayName,