Skip to content

Commit f527fd1

Browse files
committed
Track the key path difference between right before the first array (#27360)
There's a subtle difference if you suspend before the first array or after. In Fiber, we don't deal with this because we just suspend the parent and replay it if lazy() or Usable are used in its child slots. In Fizz we try to optimize this a bit more and enable resuming inside the component. Semantically, it's different if you suspend/postpone before the first child array or inside that child array. Because when you resume the inner result might be another array and either that's part of the parent path or part of the inner slot. There might be more clever way of structuring this but I just use -1 to indicate that we're not yet inside the array and is in the root child position. If that renders an element, then that's just the same as the 0 slot. We need to also encode this in the resuming. I called that resuming the element or resuming the slot. DiffTrain build for [7a3cb8f](7a3cb8f)
1 parent 998c193 commit f527fd1

8 files changed

+236
-374
lines changed

compiled/facebook-www/REVISION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
bbc8530ed7a67859583a7c990ac51cd39c7746e5
1+
7a3cb8f9cf43591afc74722ece9e3216ccc98128

compiled/facebook-www/ReactDOMServer-dev.classic.js

Lines changed: 27 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ if (__DEV__) {
1919
var React = require("react");
2020
var ReactDOM = require("react-dom");
2121

22-
var ReactVersion = "18.3.0-www-classic-51adb8d6";
22+
var ReactVersion = "18.3.0-www-classic-d167ae1b";
2323

2424
// This refers to a WWW module.
2525
var warningWWW = require("warning");
@@ -9990,7 +9990,7 @@ function renderSuspenseBoundary(request, task, props) {
99909990

99919991
try {
99929992
// We use the safe form because we don't handle suspending here. Only error handling.
9993-
renderNode(request, task, content, 0);
9993+
renderNode(request, task, content, -1);
99949994
pushSegmentFinale(
99959995
contentRootSegment.chunks,
99969996
request.renderState,
@@ -10077,7 +10077,7 @@ function renderHostElement(request, task, type, props) {
1007710077
task.formatContext = getChildFormatContext(prevContext, type, props); // We use the non-destructive form because if something suspends, we still
1007810078
// need to pop back up and finish this subtree of HTML.
1007910079

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

1008310083
task.formatContext = prevContext;
@@ -10139,13 +10139,13 @@ function finishClassComponent(request, task, instance, Component, props) {
1013910139
childContextTypes
1014010140
);
1014110141
task.legacyContext = mergedContext;
10142-
renderNodeDestructive(request, task, null, nextChildren, 0);
10142+
renderNodeDestructive(request, task, null, nextChildren, -1);
1014310143
task.legacyContext = previousContext;
1014410144
return;
1014510145
}
1014610146
}
1014710147

10148-
renderNodeDestructive(request, task, null, nextChildren, 0);
10148+
renderNodeDestructive(request, task, null, nextChildren, -1);
1014910149
}
1015010150

1015110151
function renderClassComponent(request, task, Component, props) {
@@ -10302,20 +10302,20 @@ function finishFunctionComponent(
1030210302
// suspends or errors, we'll use the non-destructive render path.
1030310303

1030410304
task.treeContext = pushTreeContext(prevTreeContext, totalChildren, index);
10305-
renderNode(request, task, children, 0); // Like the other contexts, this does not need to be in a finally block
10305+
renderNode(request, task, children, -1); // Like the other contexts, this does not need to be in a finally block
1030610306
// because renderNode takes care of unwinding the stack.
1030710307

1030810308
task.treeContext = prevTreeContext;
1030910309
} else if (didEmitFormStateMarkers) {
1031010310
// If there were formState hooks, we must use the non-destructive path
1031110311
// because this component is not a pure indirection; we emitted markers
1031210312
// to the stream.
10313-
renderNode(request, task, children, 0);
10313+
renderNode(request, task, children, -1);
1031410314
} else {
1031510315
// We're now successfully past this task, and we haven't modified the
1031610316
// context stack. We don't have to pop back to the previous task every
1031710317
// again, so we can use the destructive recursive form.
10318-
renderNodeDestructive(request, task, null, children, 0);
10318+
renderNodeDestructive(request, task, null, children, -1);
1031910319
}
1032010320
}
1032110321

@@ -10473,7 +10473,7 @@ function renderContextConsumer(request, task, context, props) {
1047310473

1047410474
var newValue = readContext$1(context);
1047510475
var newChildren = render(newValue);
10476-
renderNodeDestructive(request, task, null, newChildren, 0);
10476+
renderNodeDestructive(request, task, null, newChildren, -1);
1047710477
}
1047810478

1047910479
function renderContextProvider(request, task, type, props) {
@@ -10487,7 +10487,7 @@ function renderContextProvider(request, task, type, props) {
1048710487
}
1048810488

1048910489
task.context = pushProvider(context, value);
10490-
renderNodeDestructive(request, task, null, children, 0);
10490+
renderNodeDestructive(request, task, null, children, -1);
1049110491
task.context = popProvider(context);
1049210492

1049310493
{
@@ -10530,7 +10530,7 @@ function renderOffscreen(request, task, props) {
1053010530
else {
1053110531
// A visible Offscreen boundary is treated exactly like a fragment: a
1053210532
// pure indirection.
10533-
renderNodeDestructive(request, task, null, props.children, 0);
10533+
renderNodeDestructive(request, task, null, props.children, -1);
1053410534
}
1053510535
}
1053610536

@@ -10571,7 +10571,7 @@ function renderElement(request, task, prevThenableState, type, props, ref) {
1057110571
case REACT_STRICT_MODE_TYPE:
1057210572
case REACT_PROFILER_TYPE:
1057310573
case REACT_FRAGMENT_TYPE: {
10574-
renderNodeDestructive(request, task, null, props.children, 0);
10574+
renderNodeDestructive(request, task, null, props.children, -1);
1057510575
return;
1057610576
}
1057710577

@@ -10583,14 +10583,14 @@ function renderElement(request, task, prevThenableState, type, props, ref) {
1058310583
case REACT_SUSPENSE_LIST_TYPE: {
1058410584
pushBuiltInComponentStackInDEV(task, "SuspenseList"); // TODO: SuspenseList should control the boundaries.
1058510585

10586-
renderNodeDestructive(request, task, null, props.children, 0);
10586+
renderNodeDestructive(request, task, null, props.children, -1);
1058710587
popComponentStackInDEV(task);
1058810588
return;
1058910589
}
1059010590

1059110591
case REACT_SCOPE_TYPE: {
1059210592
{
10593-
renderNodeDestructive(request, task, null, props.children, 0);
10593+
renderNodeDestructive(request, task, null, props.children, -1);
1059410594
return;
1059510595
}
1059610596
}
@@ -10748,7 +10748,11 @@ function renderNodeDestructiveImpl(
1074810748
var ref = element.ref;
1074910749
var name = getComponentNameFromType(type);
1075010750
var prevKeyPath = task.keyPath;
10751-
task.keyPath = [task.keyPath, name, key == null ? childIndex : key];
10751+
task.keyPath = [
10752+
task.keyPath,
10753+
name,
10754+
key == null ? (childIndex === -1 ? 0 : childIndex) : key
10755+
];
1075210756
renderElement(request, task, prevThenableState, type, props, ref);
1075310757
task.keyPath = prevKeyPath;
1075410758
return;
@@ -10910,59 +10914,26 @@ function renderNodeDestructiveImpl(
1091010914
}
1091110915

1091210916
function renderChildrenArray(request, task, children, childIndex) {
10917+
var prevKeyPath = task.keyPath;
10918+
10919+
if (childIndex !== -1) {
10920+
task.keyPath = [task.keyPath, "", childIndex];
10921+
}
10922+
1091310923
var prevTreeContext = task.treeContext;
1091410924
var totalChildren = children.length;
1091510925

1091610926
for (var i = 0; i < totalChildren; i++) {
1091710927
var node = children[i];
10918-
task.treeContext = pushTreeContext(prevTreeContext, totalChildren, i); // Nested arrays behave like a "fragment node" which is keyed.
10919-
// Therefore we need to add the current index as a parent key.
10920-
// We first check if the nested nodes are arrays or iterables.
10921-
10922-
if (isArray(node)) {
10923-
var prevKeyPath = task.keyPath;
10924-
task.keyPath = [task.keyPath, "", childIndex];
10925-
renderChildrenArray(request, task, node, i);
10926-
task.keyPath = prevKeyPath;
10927-
continue;
10928-
}
10929-
10930-
var iteratorFn = getIteratorFn(node);
10931-
10932-
if (iteratorFn) {
10933-
{
10934-
validateIterable(node, iteratorFn);
10935-
}
10936-
10937-
var iterator = iteratorFn.call(node);
10938-
10939-
if (iterator) {
10940-
var step = iterator.next();
10941-
10942-
if (!step.done) {
10943-
var _prevKeyPath = task.keyPath;
10944-
task.keyPath = [task.keyPath, "", childIndex];
10945-
var nestedChildren = [];
10946-
10947-
do {
10948-
nestedChildren.push(step.value);
10949-
step = iterator.next();
10950-
} while (!step.done);
10951-
10952-
renderChildrenArray(request, task, nestedChildren, i);
10953-
task.keyPath = _prevKeyPath;
10954-
}
10955-
10956-
continue;
10957-
}
10958-
} // We need to use the non-destructive form so that we can safely pop back
10928+
task.treeContext = pushTreeContext(prevTreeContext, totalChildren, i); // We need to use the non-destructive form so that we can safely pop back
1095910929
// up and render the sibling if something suspends.
1096010930

1096110931
renderNode(request, task, node, i);
1096210932
} // Because this context is always set right before rendering every child, we
1096310933
// only need to reset it to the previous value at the very end.
1096410934

1096510935
task.treeContext = prevTreeContext;
10936+
task.keyPath = prevKeyPath;
1096610937
}
1096710938

1096810939
function spawnNewSuspendedTask(request, task, thenableState, x) {

compiled/facebook-www/ReactDOMServer-dev.modern.js

Lines changed: 26 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ if (__DEV__) {
1919
var React = require("react");
2020
var ReactDOM = require("react-dom");
2121

22-
var ReactVersion = "18.3.0-www-modern-78c6f394";
22+
var ReactVersion = "18.3.0-www-modern-2efbee1a";
2323

2424
// This refers to a WWW module.
2525
var warningWWW = require("warning");
@@ -9749,7 +9749,7 @@ function renderSuspenseBoundary(request, task, props) {
97499749

97509750
try {
97519751
// We use the safe form because we don't handle suspending here. Only error handling.
9752-
renderNode(request, task, content, 0);
9752+
renderNode(request, task, content, -1);
97539753
pushSegmentFinale(
97549754
contentRootSegment.chunks,
97559755
request.renderState,
@@ -9836,7 +9836,7 @@ function renderHostElement(request, task, type, props) {
98369836
task.formatContext = getChildFormatContext(prevContext, type, props); // We use the non-destructive form because if something suspends, we still
98379837
// need to pop back up and finish this subtree of HTML.
98389838

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

98429842
task.formatContext = prevContext;
@@ -9886,7 +9886,7 @@ function finishClassComponent(request, task, instance, Component, props) {
98869886
}
98879887
}
98889888

9889-
renderNodeDestructive(request, task, null, nextChildren, 0);
9889+
renderNodeDestructive(request, task, null, nextChildren, -1);
98909890
}
98919891

98929892
function renderClassComponent(request, task, Component, props) {
@@ -10050,20 +10050,20 @@ function finishFunctionComponent(
1005010050
// suspends or errors, we'll use the non-destructive render path.
1005110051

1005210052
task.treeContext = pushTreeContext(prevTreeContext, totalChildren, index);
10053-
renderNode(request, task, children, 0); // Like the other contexts, this does not need to be in a finally block
10053+
renderNode(request, task, children, -1); // Like the other contexts, this does not need to be in a finally block
1005410054
// because renderNode takes care of unwinding the stack.
1005510055

1005610056
task.treeContext = prevTreeContext;
1005710057
} else if (didEmitFormStateMarkers) {
1005810058
// If there were formState hooks, we must use the non-destructive path
1005910059
// because this component is not a pure indirection; we emitted markers
1006010060
// to the stream.
10061-
renderNode(request, task, children, 0);
10061+
renderNode(request, task, children, -1);
1006210062
} else {
1006310063
// We're now successfully past this task, and we haven't modified the
1006410064
// context stack. We don't have to pop back to the previous task every
1006510065
// again, so we can use the destructive recursive form.
10066-
renderNodeDestructive(request, task, null, children, 0);
10066+
renderNodeDestructive(request, task, null, children, -1);
1006710067
}
1006810068
}
1006910069

@@ -10221,7 +10221,7 @@ function renderContextConsumer(request, task, context, props) {
1022110221

1022210222
var newValue = readContext$1(context);
1022310223
var newChildren = render(newValue);
10224-
renderNodeDestructive(request, task, null, newChildren, 0);
10224+
renderNodeDestructive(request, task, null, newChildren, -1);
1022510225
}
1022610226

1022710227
function renderContextProvider(request, task, type, props) {
@@ -10235,7 +10235,7 @@ function renderContextProvider(request, task, type, props) {
1023510235
}
1023610236

1023710237
task.context = pushProvider(context, value);
10238-
renderNodeDestructive(request, task, null, children, 0);
10238+
renderNodeDestructive(request, task, null, children, -1);
1023910239
task.context = popProvider(context);
1024010240

1024110241
{
@@ -10278,7 +10278,7 @@ function renderOffscreen(request, task, props) {
1027810278
else {
1027910279
// A visible Offscreen boundary is treated exactly like a fragment: a
1028010280
// pure indirection.
10281-
renderNodeDestructive(request, task, null, props.children, 0);
10281+
renderNodeDestructive(request, task, null, props.children, -1);
1028210282
}
1028310283
}
1028410284

@@ -10319,7 +10319,7 @@ function renderElement(request, task, prevThenableState, type, props, ref) {
1031910319
case REACT_STRICT_MODE_TYPE:
1032010320
case REACT_PROFILER_TYPE:
1032110321
case REACT_FRAGMENT_TYPE: {
10322-
renderNodeDestructive(request, task, null, props.children, 0);
10322+
renderNodeDestructive(request, task, null, props.children, -1);
1032310323
return;
1032410324
}
1032510325

@@ -10331,14 +10331,14 @@ function renderElement(request, task, prevThenableState, type, props, ref) {
1033110331
case REACT_SUSPENSE_LIST_TYPE: {
1033210332
pushBuiltInComponentStackInDEV(task, "SuspenseList"); // TODO: SuspenseList should control the boundaries.
1033310333

10334-
renderNodeDestructive(request, task, null, props.children, 0);
10334+
renderNodeDestructive(request, task, null, props.children, -1);
1033510335
popComponentStackInDEV(task);
1033610336
return;
1033710337
}
1033810338

1033910339
case REACT_SCOPE_TYPE: {
1034010340
{
10341-
renderNodeDestructive(request, task, null, props.children, 0);
10341+
renderNodeDestructive(request, task, null, props.children, -1);
1034210342
return;
1034310343
}
1034410344
}
@@ -10496,7 +10496,11 @@ function renderNodeDestructiveImpl(
1049610496
var ref = element.ref;
1049710497
var name = getComponentNameFromType(type);
1049810498
var prevKeyPath = task.keyPath;
10499-
task.keyPath = [task.keyPath, name, key == null ? childIndex : key];
10499+
task.keyPath = [
10500+
task.keyPath,
10501+
name,
10502+
key == null ? (childIndex === -1 ? 0 : childIndex) : key
10503+
];
1050010504
renderElement(request, task, prevThenableState, type, props, ref);
1050110505
task.keyPath = prevKeyPath;
1050210506
return;
@@ -10658,59 +10662,26 @@ function renderNodeDestructiveImpl(
1065810662
}
1065910663

1066010664
function renderChildrenArray(request, task, children, childIndex) {
10665+
var prevKeyPath = task.keyPath;
10666+
10667+
if (childIndex !== -1) {
10668+
task.keyPath = [task.keyPath, "", childIndex];
10669+
}
10670+
1066110671
var prevTreeContext = task.treeContext;
1066210672
var totalChildren = children.length;
1066310673

1066410674
for (var i = 0; i < totalChildren; i++) {
1066510675
var node = children[i];
10666-
task.treeContext = pushTreeContext(prevTreeContext, totalChildren, i); // Nested arrays behave like a "fragment node" which is keyed.
10667-
// Therefore we need to add the current index as a parent key.
10668-
// We first check if the nested nodes are arrays or iterables.
10669-
10670-
if (isArray(node)) {
10671-
var prevKeyPath = task.keyPath;
10672-
task.keyPath = [task.keyPath, "", childIndex];
10673-
renderChildrenArray(request, task, node, i);
10674-
task.keyPath = prevKeyPath;
10675-
continue;
10676-
}
10677-
10678-
var iteratorFn = getIteratorFn(node);
10679-
10680-
if (iteratorFn) {
10681-
{
10682-
validateIterable(node, iteratorFn);
10683-
}
10684-
10685-
var iterator = iteratorFn.call(node);
10686-
10687-
if (iterator) {
10688-
var step = iterator.next();
10689-
10690-
if (!step.done) {
10691-
var _prevKeyPath = task.keyPath;
10692-
task.keyPath = [task.keyPath, "", childIndex];
10693-
var nestedChildren = [];
10694-
10695-
do {
10696-
nestedChildren.push(step.value);
10697-
step = iterator.next();
10698-
} while (!step.done);
10699-
10700-
renderChildrenArray(request, task, nestedChildren, i);
10701-
task.keyPath = _prevKeyPath;
10702-
}
10703-
10704-
continue;
10705-
}
10706-
} // We need to use the non-destructive form so that we can safely pop back
10676+
task.treeContext = pushTreeContext(prevTreeContext, totalChildren, i); // We need to use the non-destructive form so that we can safely pop back
1070710677
// up and render the sibling if something suspends.
1070810678

1070910679
renderNode(request, task, node, i);
1071010680
} // Because this context is always set right before rendering every child, we
1071110681
// only need to reset it to the previous value at the very end.
1071210682

1071310683
task.treeContext = prevTreeContext;
10684+
task.keyPath = prevKeyPath;
1071410685
}
1071510686

1071610687
function spawnNewSuspendedTask(request, task, thenableState, x) {

0 commit comments

Comments
 (0)