Skip to content

Commit 356c3bd

Browse files
committed
Simplify coroutines by making yields stateless
Coroutines was kind of broken because it tried to do reparenting and enabling state preservation to be passed along the coroutine. However, since we couldn't determine which Fiber was "current" on a reified yield this was kind of broken. This removes the "continuation" part of yields so they're basically just return values. It is still possible to do continuations by just passing simple functions or classes as part of the return value but they're not stateful. This means that we won't have reparenting, but I actually don't think we need it. There's another way to structure this by doing all the state in the first phase and then yielding a stateless representation of the result. This stateless representation of the tree can then be rendered in different (or even multiple) locations. Because we no longer have a stateful continuation, you may have noticed that this really no longer represent the "coroutine" concept. I will rename it in a follow up commit.
1 parent 1293ec3 commit 356c3bd

File tree

7 files changed

+45
-118
lines changed

7 files changed

+45
-118
lines changed

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 {
@@ -318,21 +312,16 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
318312
yieldNode : ReactYield,
319313
priority : PriorityLevel
320314
) : Fiber {
321-
// TODO: Should this also compare continuation to determine whether to reuse?
322315
if (current == null || current.tag !== YieldComponent) {
323316
// Insert
324-
const reifiedYield = createReifiedYield(yieldNode);
325317
const created = createFiberFromYield(yieldNode, priority);
326-
created.type = reifiedYield;
318+
created.type = yieldNode.value;
327319
created.return = returnFiber;
328320
return created;
329321
} else {
330322
// Move based on index
331323
const existing = useFiber(current, priority);
332-
existing.type = createUpdatedReifiedYield(
333-
current.type,
334-
yieldNode
335-
);
324+
existing.type = yieldNode.value;
336325
existing.return = returnFiber;
337326
return existing;
338327
}
@@ -413,9 +402,8 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
413402
}
414403

415404
case REACT_YIELD_TYPE: {
416-
const reifiedYield = createReifiedYield(newChild);
417405
const created = createFiberFromYield(newChild, priority);
418-
created.type = reifiedYield;
406+
created.type = newChild.value;
419407
created.return = returnFiber;
420408
return created;
421409
}
@@ -476,7 +464,10 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
476464
}
477465

478466
case REACT_YIELD_TYPE: {
479-
if (newChild.key === key) {
467+
// Yields doesn't have keys. If the previous node is implicitly keyed
468+
// we can continue to replace it without aborting even if it is not a
469+
// yield.
470+
if (key === null) {
480471
return updateYield(returnFiber, oldFiber, newChild, priority);
481472
} else {
482473
return null;
@@ -529,9 +520,9 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
529520
}
530521

531522
case REACT_YIELD_TYPE: {
532-
const matchedFiber = existingChildren.get(
533-
newChild.key === null ? newIdx : newChild.key
534-
) || null;
523+
// Yields doesn't have keys, so we neither have to check the old nor
524+
// new node for the key. If both are yields, they match.
525+
const matchedFiber = existingChildren.get(newIdx) || null;
535526
return updateYield(returnFiber, matchedFiber, newChild, priority);
536527
}
537528

@@ -563,7 +554,6 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
563554
switch (child.$$typeof) {
564555
case REACT_ELEMENT_TYPE:
565556
case REACT_COROUTINE_TYPE:
566-
case REACT_YIELD_TYPE:
567557
case REACT_PORTAL_TYPE:
568558
const key = child.key;
569559
if (typeof key !== 'string') {
@@ -1021,34 +1011,22 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
10211011
yieldNode : ReactYield,
10221012
priority : PriorityLevel
10231013
) : Fiber {
1024-
const key = yieldNode.key;
1014+
// There's no need to check for keys on yields since they're stateless.
10251015
let child = currentFirstChild;
1026-
while (child) {
1027-
// TODO: If key === null and child.key === null, then this only applies to
1028-
// the first item in the list.
1029-
if (child.key === key) {
1030-
if (child.tag === YieldComponent) {
1031-
deleteRemainingChildren(returnFiber, child.sibling);
1032-
const existing = useFiber(child, priority);
1033-
existing.type = createUpdatedReifiedYield(
1034-
child.type,
1035-
yieldNode
1036-
);
1037-
existing.return = returnFiber;
1038-
return existing;
1039-
} else {
1040-
deleteRemainingChildren(returnFiber, child);
1041-
break;
1042-
}
1016+
if (child) {
1017+
if (child.tag === YieldComponent) {
1018+
deleteRemainingChildren(returnFiber, child.sibling);
1019+
const existing = useFiber(child, priority);
1020+
existing.type = yieldNode.value;
1021+
existing.return = returnFiber;
1022+
return existing;
10431023
} else {
1044-
deleteChild(returnFiber, child);
1024+
deleteRemainingChildren(returnFiber, child);
10451025
}
1046-
child = child.sibling;
10471026
}
10481027

1049-
const reifiedYield = createReifiedYield(yieldNode);
10501028
const created = createFiberFromYield(yieldNode, priority);
1051-
created.type = reifiedYield;
1029+
created.type = yieldNode.value;
10521030
created.return = returnFiber;
10531031
return created;
10541032
}

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/ReactFiberCompleteWork.js

Lines changed: 2 additions & 3 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 {
@@ -84,7 +83,7 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
8483
workInProgress.effectTag |= Update;
8584
}
8685

87-
function appendAllYields(yields : Array<ReifiedYield>, workInProgress : Fiber) {
86+
function appendAllYields(yields : Array<mixed>, workInProgress : Fiber) {
8887
let node = workInProgress.stateNode;
8988
while (node) {
9089
if (node.tag === HostComponent || node.tag === HostText ||
@@ -128,7 +127,7 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
128127

129128
// Build up the yields.
130129
// TODO: Compare this to a generator or opaque helpers like Children.
131-
var yields : Array<ReifiedYield> = [];
130+
var yields : Array<mixed> = [];
132131
appendAllYields(yields, workInProgress);
133132
var fn = coroutine.handler;
134133
var props = coroutine.props;

src/renderers/shared/fiber/ReactReifiedYield.js

Lines changed: 0 additions & 47 deletions
This file was deleted.

src/renderers/shared/fiber/__tests__/ReactCoroutine-test.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,11 @@ describe('ReactCoroutine', () => {
3636
function Child({ bar }) {
3737
ops.push(['Child', bar]);
3838
return ReactCoroutine.createYield({
39-
bar: bar,
40-
}, Continuation, null);
39+
props: {
40+
bar: bar,
41+
},
42+
continuation: Continuation,
43+
});
4144
}
4245

4346
function Indirection() {
@@ -100,7 +103,7 @@ describe('ReactCoroutine', () => {
100103
class Child extends React.Component {
101104
render() {
102105
ops.push('Child');
103-
return ReactCoroutine.createYield({}, Continuation, null);
106+
return ReactCoroutine.createYield(Continuation);
104107
}
105108
componentWillUnmount() {
106109
ops.push('Unmount Child');
@@ -109,7 +112,7 @@ describe('ReactCoroutine', () => {
109112

110113
function HandleYields(props, yields) {
111114
ops.push('HandleYields');
112-
return yields.map(y => <y.continuation />);
115+
return yields.map(ContinuationComponent => <ContinuationComponent />);
113116
}
114117

115118
class Parent extends React.Component {

src/renderers/shared/fiber/isomorphic/ReactCoroutine.js

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -16,30 +16,29 @@ import type { ReactNodeList } from 'ReactTypes';
1616

1717
// The Symbol used to tag the special React types. If there is no native Symbol
1818
// nor polyfill, then a plain number is used for performance.
19-
var REACT_COROUTINE_TYPE =
20-
(typeof Symbol === 'function' && Symbol.for && Symbol.for('react.coroutine')) ||
21-
0xeac8;
19+
var REACT_COROUTINE_TYPE;
20+
var REACT_YIELD_TYPE;
21+
if (typeof Symbol === 'function' && Symbol.for) {
22+
REACT_COROUTINE_TYPE = Symbol.for('react.coroutine');
23+
REACT_YIELD_TYPE = Symbol.for('react.yield');
24+
} else {
25+
REACT_COROUTINE_TYPE = 0xeac8;
26+
REACT_YIELD_TYPE = 0xeac9;
27+
}
2228

23-
var REACT_YIELD_TYPE =
24-
(typeof Symbol === 'function' && Symbol.for && Symbol.for('react.yield')) ||
25-
0xeac9;
26-
27-
type ReifiedYield = { continuation: Object, props: Object };
28-
type CoroutineHandler<T> = (props: T, yields: Array<ReifiedYield>) => ReactNodeList;
29+
type CoroutineHandler<T> = (props: T, yields: Array<mixed>) => ReactNodeList;
2930

3031
export type ReactCoroutine = {
3132
$$typeof: Symbol | number,
3233
key: null | string,
3334
children: any,
3435
// This should be a more specific CoroutineHandler
35-
handler: (props: any, yields: Array<ReifiedYield>) => ReactNodeList,
36+
handler: (props: any, yields: Array<mixed>) => ReactNodeList,
3637
props: any,
3738
};
3839
export type ReactYield = {
3940
$$typeof: Symbol | number,
40-
key: null | string,
41-
props: Object,
42-
continuation: mixed
41+
value: mixed,
4342
};
4443

4544
exports.createCoroutine = function<T>(
@@ -68,19 +67,16 @@ exports.createCoroutine = function<T>(
6867
return coroutine;
6968
};
7069

71-
exports.createYield = function(props : mixed, continuation : mixed, key : ?string = null) {
70+
exports.createYield = function(value : mixed) : ReactYield {
7271
var yieldNode = {
7372
// This tag allow us to uniquely identify this as a React Yield
7473
$$typeof: REACT_YIELD_TYPE,
75-
key: key == null ? null : '' + key,
76-
props: props,
77-
continuation: continuation,
74+
value: value,
7875
};
7976

8077
if (__DEV__) {
8178
// TODO: Add _store property for marking this as validated.
8279
if (Object.freeze) {
83-
Object.freeze(yieldNode.props);
8480
Object.freeze(yieldNode);
8581
}
8682
}

0 commit comments

Comments
 (0)