Skip to content

Commit b262657

Browse files
authored
Merge pull request #8840 from sebmarkbage/statelesscoroutines
[Fiber] Simplify coroutines by making yields stateless
2 parents 39472f1 + 975ad92 commit b262657

File tree

13 files changed

+226
-140
lines changed

13 files changed

+226
-140
lines changed

scripts/fiber/tests-passing.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1143,7 +1143,9 @@ src/renderers/shared/__tests__/ReactPerf-test.js
11431143

11441144
src/renderers/shared/fiber/__tests__/ReactCoroutine-test.js
11451145
* should render a coroutine
1146+
* should update a coroutine
11461147
* should unmount a composite in a coroutine
1148+
* should handle deep updates in coroutine
11471149

11481150
src/renderers/shared/fiber/__tests__/ReactIncremental-test.js
11491151
* should render a simple component

src/isomorphic/classic/element/ReactElementValidator.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -200,8 +200,7 @@ var ReactElementValidator = {
200200
createElement: function(type, props, children) {
201201
var validType =
202202
typeof type === 'string' ||
203-
typeof type === 'function' ||
204-
type !== null && typeof type === 'object' && typeof type.tag === 'number';
203+
typeof type === 'function';
205204
// We warn in this case but don't throw. We expect the element creation to
206205
// succeed and there will likely be errors in render.
207206
if (!validType) {

src/renderers/shared/fiber/ReactChildFiber.js

Lines changed: 20 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ var {
2929
} = require('ReactPortal');
3030

3131
var ReactFiber = require('ReactFiber');
32-
var ReactReifiedYield = require('ReactReifiedYield');
3332
var ReactTypeOfSideEffect = require('ReactTypeOfSideEffect');
3433
var ReactTypeOfWork = require('ReactTypeOfWork');
3534

@@ -53,11 +52,6 @@ const {
5352
createFiberFromPortal,
5453
} = ReactFiber;
5554

56-
const {
57-
createReifiedYield,
58-
createUpdatedReifiedYield,
59-
} = ReactReifiedYield;
60-
6155
const isArray = Array.isArray;
6256

6357
const {
@@ -316,21 +310,16 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
316310
yieldNode : ReactYield,
317311
priority : PriorityLevel
318312
) : Fiber {
319-
// TODO: Should this also compare continuation to determine whether to reuse?
320313
if (current == null || current.tag !== YieldComponent) {
321314
// Insert
322-
const reifiedYield = createReifiedYield(yieldNode);
323315
const created = createFiberFromYield(yieldNode, priority);
324-
created.type = reifiedYield;
316+
created.type = yieldNode.value;
325317
created.return = returnFiber;
326318
return created;
327319
} else {
328320
// Move based on index
329321
const existing = useFiber(current, priority);
330-
existing.type = createUpdatedReifiedYield(
331-
current.type,
332-
yieldNode
333-
);
322+
existing.type = yieldNode.value;
334323
existing.return = returnFiber;
335324
return existing;
336325
}
@@ -411,9 +400,8 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
411400
}
412401

413402
case REACT_YIELD_TYPE: {
414-
const reifiedYield = createReifiedYield(newChild);
415403
const created = createFiberFromYield(newChild, priority);
416-
created.type = reifiedYield;
404+
created.type = newChild.value;
417405
created.return = returnFiber;
418406
return created;
419407
}
@@ -474,7 +462,10 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
474462
}
475463

476464
case REACT_YIELD_TYPE: {
477-
if (newChild.key === key) {
465+
// Yields doesn't have keys. If the previous node is implicitly keyed
466+
// we can continue to replace it without aborting even if it is not a
467+
// yield.
468+
if (key === null) {
478469
return updateYield(returnFiber, oldFiber, newChild, priority);
479470
} else {
480471
return null;
@@ -527,9 +518,9 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
527518
}
528519

529520
case REACT_YIELD_TYPE: {
530-
const matchedFiber = existingChildren.get(
531-
newChild.key === null ? newIdx : newChild.key
532-
) || null;
521+
// Yields doesn't have keys, so we neither have to check the old nor
522+
// new node for the key. If both are yields, they match.
523+
const matchedFiber = existingChildren.get(newIdx) || null;
533524
return updateYield(returnFiber, matchedFiber, newChild, priority);
534525
}
535526

@@ -561,7 +552,6 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
561552
switch (child.$$typeof) {
562553
case REACT_ELEMENT_TYPE:
563554
case REACT_COROUTINE_TYPE:
564-
case REACT_YIELD_TYPE:
565555
case REACT_PORTAL_TYPE:
566556
const key = child.key;
567557
if (typeof key !== 'string') {
@@ -1019,34 +1009,22 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
10191009
yieldNode : ReactYield,
10201010
priority : PriorityLevel
10211011
) : Fiber {
1022-
const key = yieldNode.key;
1012+
// There's no need to check for keys on yields since they're stateless.
10231013
let child = currentFirstChild;
1024-
while (child) {
1025-
// TODO: If key === null and child.key === null, then this only applies to
1026-
// the first item in the list.
1027-
if (child.key === key) {
1028-
if (child.tag === YieldComponent) {
1029-
deleteRemainingChildren(returnFiber, child.sibling);
1030-
const existing = useFiber(child, priority);
1031-
existing.type = createUpdatedReifiedYield(
1032-
child.type,
1033-
yieldNode
1034-
);
1035-
existing.return = returnFiber;
1036-
return existing;
1037-
} else {
1038-
deleteRemainingChildren(returnFiber, child);
1039-
break;
1040-
}
1014+
if (child) {
1015+
if (child.tag === YieldComponent) {
1016+
deleteRemainingChildren(returnFiber, child.sibling);
1017+
const existing = useFiber(child, priority);
1018+
existing.type = yieldNode.value;
1019+
existing.return = returnFiber;
1020+
return existing;
10411021
} else {
1042-
deleteChild(returnFiber, child);
1022+
deleteRemainingChildren(returnFiber, child);
10431023
}
1044-
child = child.sibling;
10451024
}
10461025

1047-
const reifiedYield = createReifiedYield(yieldNode);
10481026
const created = createFiberFromYield(yieldNode, priority);
1049-
created.type = reifiedYield;
1027+
created.type = yieldNode.value;
10501028
created.return = returnFiber;
10511029
return created;
10521030
}

src/renderers/shared/fiber/ReactFiber.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -393,8 +393,7 @@ exports.createFiberFromCoroutine = function(coroutine : ReactCoroutine, priority
393393
};
394394

395395
exports.createFiberFromYield = function(yieldNode : ReactYield, priorityLevel : PriorityLevel) : Fiber {
396-
const fiber = createFiber(YieldComponent, yieldNode.key);
397-
fiber.pendingProps = {};
396+
const fiber = createFiber(YieldComponent, null);
398397
return fiber;
399398
};
400399

src/renderers/shared/fiber/ReactFiberBeginWork.js

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -516,13 +516,54 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
516516
}
517517
}
518518
} else if (nextCoroutine === null || workInProgress.memoizedProps === nextCoroutine) {
519-
return bailoutOnAlreadyFinishedWork(current, workInProgress);
519+
nextCoroutine = workInProgress.memoizedProps;
520+
// TODO: When bailing out, we might need to return the stateNode instead
521+
// of the child. To check it for work.
522+
// return bailoutOnAlreadyFinishedWork(current, workInProgress);
523+
}
524+
525+
const nextChildren = nextCoroutine.children;
526+
const priorityLevel = workInProgress.pendingWorkPriority;
527+
528+
// The following is a fork of reconcileChildrenAtPriority but using
529+
// stateNode to store the child.
530+
531+
// At this point any memoization is no longer valid since we'll have changed
532+
// the children.
533+
workInProgress.memoizedProps = null;
534+
if (!current) {
535+
workInProgress.stateNode = mountChildFibersInPlace(
536+
workInProgress,
537+
workInProgress.stateNode,
538+
nextChildren,
539+
priorityLevel
540+
);
541+
} else if (current.child === workInProgress.child) {
542+
clearDeletions(workInProgress);
543+
544+
workInProgress.stateNode = reconcileChildFibers(
545+
workInProgress,
546+
workInProgress.stateNode,
547+
nextChildren,
548+
priorityLevel
549+
);
550+
551+
transferDeletions(workInProgress);
552+
} else {
553+
workInProgress.stateNode = reconcileChildFibersInPlace(
554+
workInProgress,
555+
workInProgress.stateNode,
556+
nextChildren,
557+
priorityLevel
558+
);
559+
560+
transferDeletions(workInProgress);
520561
}
521-
reconcileChildren(current, workInProgress, nextCoroutine.children);
562+
522563
memoizeProps(workInProgress, nextCoroutine);
523564
// This doesn't take arbitrary time so we could synchronously just begin
524565
// eagerly do the work of workInProgress.child as an optimization.
525-
return workInProgress.child;
566+
return workInProgress.stateNode;
526567
}
527568

528569
function updatePortalComponent(current, workInProgress) {

src/renderers/shared/fiber/ReactFiberCommitWork.js

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,6 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
136136
while (node.tag !== HostComponent && node.tag !== HostText) {
137137
// If it is not host node and, we might have a host node inside it.
138138
// Try to search down until we find one.
139-
// TODO: For coroutines, this will have to search the stateNode.
140139
if (node.effectTag & Placement) {
141140
// If we don't have a child, try the siblings instead.
142141
continue siblings;
@@ -198,7 +197,6 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
198197
// down its children. Instead, we'll get insertions from each child in
199198
// the portal directly.
200199
} else if (node.child) {
201-
// TODO: Coroutines need to visit the stateNode.
202200
node.child.return = node;
203201
node = node.child;
204202
continue;
@@ -229,7 +227,6 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
229227
// Visit children because they may contain more composite or host nodes.
230228
// Skip portals because commitUnmount() currently visits them recursively.
231229
if (node.child && node.tag !== HostPortal) {
232-
// TODO: Coroutines need to visit the stateNode.
233230
node.child.return = node;
234231
node = node.child;
235232
continue;
@@ -273,7 +270,6 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
273270
commitUnmount(node);
274271
// Visit children because we may find more host components below.
275272
if (node.child) {
276-
// TODO: Coroutines need to visit the stateNode.
277273
node.child.return = node;
278274
node = node.child;
279275
continue;

src/renderers/shared/fiber/ReactFiberCompleteWork.js

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ import type { Fiber } from 'ReactFiber';
1717
import type { HostContext } from 'ReactFiberHostContext';
1818
import type { FiberRoot } from 'ReactFiberRoot';
1919
import type { HostConfig } from 'ReactFiberReconciler';
20-
import type { ReifiedYield } from 'ReactReifiedYield';
2120

2221
var { reconcileChildFibers } = require('ReactChildFiber');
2322
var {
@@ -66,29 +65,40 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
6665
popHostContainer,
6766
} = hostContext;
6867

68+
function markChildAsProgressed(current, workInProgress, priorityLevel) {
69+
// We now have clones. Let's store them as the currently progressed work.
70+
workInProgress.progressedChild = workInProgress.child;
71+
workInProgress.progressedPriority = priorityLevel;
72+
if (current) {
73+
// We also store it on the current. When the alternate swaps in we can
74+
// continue from this point.
75+
current.progressedChild = workInProgress.progressedChild;
76+
current.progressedPriority = workInProgress.progressedPriority;
77+
}
78+
}
79+
6980
function markUpdate(workInProgress : Fiber) {
7081
// Tag the fiber with an update effect. This turns a Placement into
7182
// an UpdateAndPlacement.
7283
workInProgress.effectTag |= Update;
7384
}
7485

75-
function appendAllYields(yields : Array<ReifiedYield>, workInProgress : Fiber) {
76-
let node = workInProgress.child;
86+
function appendAllYields(yields : Array<mixed>, workInProgress : Fiber) {
87+
let node = workInProgress.stateNode;
88+
if (node) {
89+
node.return = workInProgress;
90+
}
7791
while (node) {
7892
if (node.tag === HostComponent || node.tag === HostText ||
7993
node.tag === HostPortal) {
8094
throw new Error('A coroutine cannot have host component children.');
8195
} else if (node.tag === YieldComponent) {
8296
yields.push(node.type);
8397
} else if (node.child) {
84-
// TODO: Coroutines need to visit the stateNode.
8598
node.child.return = node;
8699
node = node.child;
87100
continue;
88101
}
89-
if (node === workInProgress) {
90-
return;
91-
}
92102
while (!node.sibling) {
93103
if (!node.return || node.return === workInProgress) {
94104
return;
@@ -117,22 +127,23 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
117127

118128
// Build up the yields.
119129
// TODO: Compare this to a generator or opaque helpers like Children.
120-
var yields : Array<ReifiedYield> = [];
130+
var yields : Array<mixed> = [];
121131
appendAllYields(yields, workInProgress);
122132
var fn = coroutine.handler;
123133
var props = coroutine.props;
124134
var nextChildren = fn(props, yields);
125135

126-
var currentFirstChild = current ? current.stateNode : null;
136+
var currentFirstChild = current ? current.child : null;
127137
// Inherit the priority of the returnFiber.
128138
const priority = workInProgress.pendingWorkPriority;
129-
workInProgress.stateNode = reconcileChildFibers(
139+
workInProgress.child = reconcileChildFibers(
130140
workInProgress,
131141
currentFirstChild,
132142
nextChildren,
133143
priority
134144
);
135-
return workInProgress.stateNode;
145+
markChildAsProgressed(current, workInProgress, priority);
146+
return workInProgress.child;
136147
}
137148

138149
function appendAllChildren(parent : I, workInProgress : Fiber) {
@@ -147,7 +158,6 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
147158
// down its children. Instead, we'll get insertions from each child in
148159
// the portal directly.
149160
} else if (node.child) {
150-
// TODO: Coroutines need to visit the stateNode.
151161
node = node.child;
152162
continue;
153163
}

src/renderers/shared/fiber/ReactFiberScheduler.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,8 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(config : HostConfig<T, P,
435435
newPriority = getPendingPriority(queue);
436436
}
437437

438+
// TODO: Coroutines need to visit stateNode
439+
438440
// progressedChild is going to be the child set with the highest priority.
439441
// Either it is the same as child, or it just bailed out because it choose
440442
// not to do the work.

src/renderers/shared/fiber/ReactFiberTreeReflection.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,6 @@ exports.findCurrentHostFiber = function(parent : Fiber) : Fiber | null {
168168
return node;
169169
} else if (node.child) {
170170
// TODO: If we hit a Portal, we're supposed to skip it.
171-
// TODO: Coroutines need to visit the stateNode.
172171
node.child.return = node;
173172
node = node.child;
174173
continue;

0 commit comments

Comments
 (0)