Skip to content

Commit

Permalink
[Fizz] Track Key Path (#27243)
Browse files Browse the repository at this point in the history
Tracks the currently executing parent path of a task, using the name of
the component and the key or index.

This can be used to uniquely identify an instance of a component between
requests - assuming nothing in the parents has changed. Even if it has
changed, if things are properly keyed, it should still line up.

It's not used yet but we'll need this for two separate features so
should land this so we can stack on top.

Can be passed to `JSON.stringify(...)` to generate a unique key.

DiffTrain build for [98f3f14](98f3f14)
  • Loading branch information
sebmarkbage committed Aug 18, 2023
1 parent 0fe197b commit ab3288b
Show file tree
Hide file tree
Showing 7 changed files with 620 additions and 308 deletions.
2 changes: 1 addition & 1 deletion compiled/facebook-www/REVISION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
e50531692010bbda2a4627b07c7c810c3770a52a
98f3f14d2e06fb785103296318204cd154d5d0ed
104 changes: 72 additions & 32 deletions compiled/facebook-www/ReactDOMServer-dev.classic.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ if (__DEV__) {
var React = require("react");
var ReactDOM = require("react-dom");

var ReactVersion = "18.3.0-www-classic-f7f9d058";
var ReactVersion = "18.3.0-www-classic-5c145eb3";

// This refers to a WWW module.
var warningWWW = require("warning");
Expand Down Expand Up @@ -9692,7 +9692,10 @@ function getStackByComponentStackNode(componentStack) {

var ReactCurrentDispatcher = ReactSharedInternals.ReactCurrentDispatcher;
var ReactCurrentCache = ReactSharedInternals.ReactCurrentCache;
var ReactDebugCurrentFrame = ReactSharedInternals.ReactDebugCurrentFrame;
var ReactDebugCurrentFrame = ReactSharedInternals.ReactDebugCurrentFrame; // Linked list representing the identity of a component given the component/tag name and key.
// The name might be minified but we assume that it's going to be the same generated name. Typically
// because it's just the same compiled output in practice.

var PENDING = 0;
var COMPLETED = 1;
var FLUSHED = 2;
Expand Down Expand Up @@ -9787,6 +9790,7 @@ function createRequest(
null,
rootSegment,
abortSet,
null,
emptyContextObject,
rootContextSnapshot,
emptyTreeContext
Expand Down Expand Up @@ -9835,6 +9839,7 @@ function createTask(
blockedBoundary,
blockedSegment,
abortSet,
keyPath,
legacyContext,
context,
treeContext
Expand All @@ -9855,6 +9860,7 @@ function createTask(
blockedBoundary: blockedBoundary,
blockedSegment: blockedSegment,
abortSet: abortSet,
keyPath: keyPath,
legacyContext: legacyContext,
context: context,
treeContext: treeContext,
Expand Down Expand Up @@ -10060,7 +10066,7 @@ function renderSuspenseBoundary(request, task, props) {

try {
// We use the safe form because we don't handle suspending here. Only error handling.
renderNode(request, task, content);
renderNode(request, task, content, 0);
pushSegmentFinale(
contentRootSegment.chunks,
request.responseState,
Expand Down Expand Up @@ -10113,6 +10119,7 @@ function renderSuspenseBoundary(request, task, props) {
parentBoundary,
boundarySegment,
fallbackAbortSet,
task.keyPath,
task.legacyContext,
task.context,
task.treeContext
Expand Down Expand Up @@ -10144,7 +10151,7 @@ function renderHostElement(request, task, type, props) {
segment.formatContext = getChildFormatContext(prevContext, type, props); // We use the non-destructive form because if something suspends, we still
// need to pop back up and finish this subtree of HTML.

renderNode(request, task, children); // We expect that errors will fatal the whole task and that we don't need
renderNode(request, task, children, 0); // We expect that errors will fatal the whole task and that we don't need
// the correct context. Therefore this is not in a finally.

segment.formatContext = prevContext;
Expand Down Expand Up @@ -10206,13 +10213,13 @@ function finishClassComponent(request, task, instance, Component, props) {
childContextTypes
);
task.legacyContext = mergedContext;
renderNodeDestructive(request, task, null, nextChildren);
renderNodeDestructive(request, task, null, nextChildren, 0);
task.legacyContext = previousContext;
return;
}
}

renderNodeDestructive(request, task, null, nextChildren);
renderNodeDestructive(request, task, null, nextChildren, 0);
}

function renderClassComponent(request, task, Component, props) {
Expand Down Expand Up @@ -10323,12 +10330,12 @@ function renderIndeterminateComponent(
task.treeContext = pushTreeContext(prevTreeContext, totalChildren, index);

try {
renderNodeDestructive(request, task, null, value);
renderNodeDestructive(request, task, null, value, 0);
} finally {
task.treeContext = prevTreeContext;
}
} else {
renderNodeDestructive(request, task, null, value);
renderNodeDestructive(request, task, null, value, 0);
}
}

Expand Down Expand Up @@ -10430,12 +10437,12 @@ function renderForwardRef(request, task, prevThenableState, type, props, ref) {
task.treeContext = pushTreeContext(prevTreeContext, totalChildren, index);

try {
renderNodeDestructive(request, task, null, children);
renderNodeDestructive(request, task, null, children, 0);
} finally {
task.treeContext = prevTreeContext;
}
} else {
renderNodeDestructive(request, task, null, children);
renderNodeDestructive(request, task, null, children, 0);
}

popComponentStackInDEV(task);
Expand Down Expand Up @@ -10497,7 +10504,7 @@ function renderContextConsumer(request, task, context, props) {

var newValue = readContext$1(context);
var newChildren = render(newValue);
renderNodeDestructive(request, task, null, newChildren);
renderNodeDestructive(request, task, null, newChildren, 0);
}

function renderContextProvider(request, task, type, props) {
Expand All @@ -10511,7 +10518,7 @@ function renderContextProvider(request, task, type, props) {
}

task.context = pushProvider(context, value);
renderNodeDestructive(request, task, null, children);
renderNodeDestructive(request, task, null, children, 0);
task.context = popProvider(context);

{
Expand Down Expand Up @@ -10554,7 +10561,7 @@ function renderOffscreen(request, task, props) {
else {
// A visible Offscreen boundary is treated exactly like a fragment: a
// pure indirection.
renderNodeDestructive(request, task, null, props.children);
renderNodeDestructive(request, task, null, props.children, 0);
}
}

Expand Down Expand Up @@ -10595,7 +10602,7 @@ function renderElement(request, task, prevThenableState, type, props, ref) {
case REACT_STRICT_MODE_TYPE:
case REACT_PROFILER_TYPE:
case REACT_FRAGMENT_TYPE: {
renderNodeDestructive(request, task, null, props.children);
renderNodeDestructive(request, task, null, props.children, 0);
return;
}

Expand All @@ -10607,14 +10614,14 @@ function renderElement(request, task, prevThenableState, type, props, ref) {
case REACT_SUSPENSE_LIST_TYPE: {
pushBuiltInComponentStackInDEV(task, "SuspenseList"); // TODO: SuspenseList should control the boundaries.

renderNodeDestructive(request, task, null, props.children);
renderNodeDestructive(request, task, null, props.children, 0);
popComponentStackInDEV(task);
return;
}

case REACT_SCOPE_TYPE: {
{
renderNodeDestructive(request, task, null, props.children);
renderNodeDestructive(request, task, null, props.children, 0);
return;
}
}
Expand Down Expand Up @@ -10719,14 +10726,21 @@ function renderNodeDestructive(
task, // The thenable state reused from the previous attempt, if any. This is almost
// always null, except when called by retryTask.
prevThenableState,
node
node,
childIndex
) {
{
// In Dev we wrap renderNodeDestructiveImpl in a try / catch so we can capture
// a component stack at the right place in the tree. We don't do this in renderNode
// becuase it is not called at every layer of the tree and we may lose frames
try {
return renderNodeDestructiveImpl(request, task, prevThenableState, node);
return renderNodeDestructiveImpl(
request,
task,
prevThenableState,
node,
childIndex
);
} catch (x) {
if (typeof x === "object" && x !== null && typeof x.then === "function");
else {
Expand All @@ -10743,7 +10757,13 @@ function renderNodeDestructive(
} // This function by it self renders a node and consumes the task by mutating it
// to update the current execution state.

function renderNodeDestructiveImpl(request, task, prevThenableState, node) {
function renderNodeDestructiveImpl(
request,
task,
prevThenableState,
node,
childIndex
) {
// Stash the node we're working on. We'll pick up from this task in case
// something suspends.
task.node = node; // Handle object types
Expand All @@ -10753,9 +10773,14 @@ function renderNodeDestructiveImpl(request, task, prevThenableState, node) {
case REACT_ELEMENT_TYPE: {
var element = node;
var type = element.type;
var key = element.key;
var props = element.props;
var ref = element.ref;
var name = getComponentNameFromType(type);
var prevKeyPath = task.keyPath;
task.keyPath = [task.keyPath, name, key == null ? childIndex : key];
renderElement(request, task, prevThenableState, type, props, ref);
task.keyPath = prevKeyPath;
return;
}

Expand Down Expand Up @@ -10791,13 +10816,13 @@ function renderNodeDestructiveImpl(request, task, prevThenableState, node) {
}
}

renderNodeDestructive(request, task, null, resolvedNode);
renderNodeDestructive(request, task, null, resolvedNode, childIndex);
return;
}
}

if (isArray(node)) {
renderChildrenArray(request, task, node);
renderChildrenArray(request, task, node, childIndex);
return;
}

Expand Down Expand Up @@ -10826,7 +10851,7 @@ function renderNodeDestructiveImpl(request, task, prevThenableState, node) {
step = iterator.next();
} while (!step.done);

renderChildrenArray(request, task, children);
renderChildrenArray(request, task, children, childIndex);
return;
}

Expand All @@ -10850,7 +10875,8 @@ function renderNodeDestructiveImpl(request, task, prevThenableState, node) {
request,
task,
null,
unwrapThenable(thenable)
unwrapThenable(thenable),
childIndex
);
}

Expand All @@ -10863,7 +10889,8 @@ function renderNodeDestructiveImpl(request, task, prevThenableState, node) {
request,
task,
null,
readContext$1(context)
readContext$1(context),
childIndex
);
} // $FlowFixMe[method-unbinding]

Expand Down Expand Up @@ -10912,19 +10939,28 @@ function renderNodeDestructiveImpl(request, task, prevThenableState, node) {
}
}

function renderChildrenArray(request, task, children) {
function renderChildrenArray(request, task, children, childIndex) {
var prevKeyPath = task.keyPath;
var totalChildren = children.length;

for (var i = 0; i < totalChildren; i++) {
var prevTreeContext = task.treeContext;
task.treeContext = pushTreeContext(prevTreeContext, totalChildren, i);

try {
// We need to use the non-destructive form so that we can safely pop back
var node = children[i];

if (isArray(node) || getIteratorFn(node)) {
// Nested arrays behave like a "fragment node" which is keyed.
// Therefore we need to add the current index as a parent key.
task.keyPath = [task.keyPath, "", childIndex];
} // We need to use the non-destructive form so that we can safely pop back
// up and render the sibling if something suspends.
renderNode(request, task, children[i]);

renderNode(request, task, node, i);
} finally {
task.treeContext = prevTreeContext;
task.keyPath = prevKeyPath;
}
}
}
Expand All @@ -10951,6 +10987,7 @@ function spawnNewSuspendedTask(request, task, thenableState, x) {
task.blockedBoundary,
newSegment,
task.abortSet,
task.keyPath,
task.legacyContext,
task.context,
task.treeContext
Expand All @@ -10969,7 +11006,7 @@ function spawnNewSuspendedTask(request, task, thenableState, x) {
} // This is a non-destructive form of rendering a node. If it suspends it spawns
// a new task and restores the context of this task to what it was before.

function renderNode(request, task, node) {
function renderNode(request, task, node, childIndex) {
// Store how much we've pushed at this point so we can reset it in case something
// suspended partially through writing something.
var segment = task.blockedSegment;
Expand All @@ -10980,14 +11017,15 @@ function renderNode(request, task, node) {
var previousFormatContext = task.blockedSegment.formatContext;
var previousLegacyContext = task.legacyContext;
var previousContext = task.context;
var previousKeyPath = task.keyPath;
var previousComponentStack = null;

{
previousComponentStack = task.componentStack;
}

try {
return renderNodeDestructive(request, task, null, node);
return renderNodeDestructive(request, task, null, node, childIndex);
} catch (thrownValue) {
resetHooksState(); // Reset the write pointers to where we started.

Expand All @@ -11010,7 +11048,8 @@ function renderNode(request, task, node) {

task.blockedSegment.formatContext = previousFormatContext;
task.legacyContext = previousLegacyContext;
task.context = previousContext; // Restore all active ReactContexts to what they were before.
task.context = previousContext;
task.keyPath = previousKeyPath; // Restore all active ReactContexts to what they were before.

switchContext(previousContext);

Expand All @@ -11024,7 +11063,8 @@ function renderNode(request, task, node) {
// functions in case nothing throws so we don't use "finally" here.
task.blockedSegment.formatContext = previousFormatContext;
task.legacyContext = previousLegacyContext;
task.context = previousContext; // Restore all active ReactContexts to what they were before.
task.context = previousContext;
task.keyPath = previousKeyPath; // Restore all active ReactContexts to what they were before.

switchContext(previousContext);

Expand Down Expand Up @@ -11286,7 +11326,7 @@ function retryTask(request, task) {
// component suspends again, the thenable state will be restored.
var prevThenableState = task.thenableState;
task.thenableState = null;
renderNodeDestructive(request, task, prevThenableState, task.node);
renderNodeDestructive(request, task, prevThenableState, task.node, 0);
pushSegmentFinale(
segment.chunks,
request.responseState,
Expand Down
Loading

0 comments on commit ab3288b

Please sign in to comment.