Add react-call-return package (#11364)
Dan Abramov committed
Oct 25, 2017 at 22:23 UTC
2c0a8fb99e945315c9dce7a15c85775d51f5755d
20 files changed
+341
-289
packages/react-call-return/README.md
new
+11
@@ -0,0 +1,11 @@
1
+# react-call-return
2
+
3
+This is an experimental package for multi-pass rendering in React.
4
+
5
+**Its API is not as stable as that of React, React Native, or React DOM, and does not follow the common versioning scheme.**
6
+
7
+**Use it at your own risk.**
8
+
9
+# API
10
+
11
+See the test case in `src/__tests__/ReactCallReturn.js` for an example.
packages/react-call-return/index.js
new
+12
@@ -0,0 +1,12 @@
1
+/**
2
+ * Copyright (c) 2013-present, Facebook, Inc.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow
8
+ */
9
+
10
+'use strict';
11
+
12
+module.exports = require('./src/ReactCallReturn');
packages/react-call-return/npm/index.js
new
+7
@@ -0,0 +1,7 @@
1
+'use strict';
2
+
3
+if (process.env.NODE_ENV === 'production') {
4
+ module.exports = require('./cjs/react-call-return.production.min.js');
5
+} else {
6
+ module.exports = require('./cjs/react-call-return.development.js');
7
+}
packages/react-call-return/package.json
new
+13
@@ -0,0 +1,13 @@
1
+{
2
+ "name": "react-call-return",
3
+ "description": "Experimental APIs for multi-pass rendering in React.",
4
+ "version": "0.1.0",
5
+ "repository": "facebook/react",
6
+ "dependencies": {
7
+ "fbjs": "^0.8.16",
8
+ "object-assign": "^4.1.1"
9
+ },
10
+ "peerDependencies": {
11
+ "react": "^16.0.0"
12
+ }
13
+}
packages/react-call-return/src/ReactCallReturn.js
new
+94
@@ -0,0 +1,94 @@
1
+/**
2
+ * Copyright (c) 2014-present, Facebook, Inc.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow
8
+ */
9
+
10
+'use strict';
11
+
12
+import type {ReactCall, ReactNodeList, ReactReturn} from 'shared/ReactTypes';
13
+
14
+// The Symbol used to tag the special React types. If there is no native Symbol
15
+// nor polyfill, then a plain number is used for performance.
16
+var REACT_CALL_TYPE;
17
+var REACT_RETURN_TYPE;
18
+if (typeof Symbol === 'function' && Symbol.for) {
19
+ REACT_CALL_TYPE = Symbol.for('react.call');
20
+ REACT_RETURN_TYPE = Symbol.for('react.return');
21
+} else {
22
+ REACT_CALL_TYPE = 0xeac8;
23
+ REACT_RETURN_TYPE = 0xeac9;
24
+}
25
+
26
+type CallHandler<T> = (props: T, returns: Array<mixed>) => ReactNodeList;
27
+
28
+exports.unstable_createCall = function<T>(
29
+ children: mixed,
30
+ handler: CallHandler<T>,
31
+ props: T,
32
+ key: ?string = null,
33
+): ReactCall {
34
+ var call = {
35
+ // This tag allow us to uniquely identify this as a React Call
36
+ $$typeof: REACT_CALL_TYPE,
37
+ key: key == null ? null : '' + key,
38
+ children: children,
39
+ handler: handler,
40
+ props: props,
41
+ };
42
+
43
+ if (__DEV__) {
44
+ // TODO: Add _store property for marking this as validated.
45
+ if (Object.freeze) {
46
+ Object.freeze(call.props);
47
+ Object.freeze(call);
48
+ }
49
+ }
50
+
51
+ return call;
52
+};
53
+
54
+exports.unstable_createReturn = function(value: mixed): ReactReturn {
55
+ var returnNode = {
56
+ // This tag allow us to uniquely identify this as a React Return
57
+ $$typeof: REACT_RETURN_TYPE,
58
+ value: value,
59
+ };
60
+
61
+ if (__DEV__) {
62
+ // TODO: Add _store property for marking this as validated.
63
+ if (Object.freeze) {
64
+ Object.freeze(returnNode);
65
+ }
66
+ }
67
+
68
+ return returnNode;
69
+};
70
+
71
+/**
72
+ * Verifies the object is a call object.
73
+ */
74
+exports.unstable_isCall = function(object: mixed): boolean {
75
+ return (
76
+ typeof object === 'object' &&
77
+ object !== null &&
78
+ object.$$typeof === REACT_CALL_TYPE
79
+ );
80
+};
81
+
82
+/**
83
+ * Verifies the object is a return object.
84
+ */
85
+exports.unstable_isReturn = function(object: mixed): boolean {
86
+ return (
87
+ typeof object === 'object' &&
88
+ object !== null &&
89
+ object.$$typeof === REACT_RETURN_TYPE
90
+ );
91
+};
92
+
93
+exports.unstable_REACT_RETURN_TYPE = REACT_RETURN_TYPE;
94
+exports.unstable_REACT_CALL_TYPE = REACT_CALL_TYPE;
packages/react-call-return/src/__tests__/ReactCallReturn-test.js
renamed
+34
-36
@@ -11,16 +11,14 @@
11
12
var React;
13
var ReactNoop;
14
-var ReactCoroutine;
14
+var ReactCallReturn;
15
16
-describe('ReactCoroutine', () => {
16
+describe('ReactCallReturn', () => {
17
beforeEach(() => {
18
jest.resetModules();
19
React = require('react');
20
ReactNoop = require('react-noop-renderer');
21
- // TODO: can we express this test with only public API?
22
- // TODO: direct imports like some-package/src/* are bad. Fix me.
23
- ReactCoroutine = require('react-reconciler/src/ReactCoroutine');
21
+ ReactCallReturn = require('react-call-return');
22
});
23
24
function div(...children) {
@@ -32,7 +30,7 @@ describe('ReactCoroutine', () => {
30
return {type: 'span', children: [], prop};
31
}
32
35
- it('should render a coroutine', () => {
33
+ it('should render a call', () => {
34
var ops = [];
35
36
function Continuation({isSame}) {
@@ -41,10 +39,10 @@ describe('ReactCoroutine', () => {
39
}
40
41
// An alternative API could mark Continuation as something that needs
44
- // yielding. E.g. Continuation.yieldType = 123;
42
+ // returning. E.g. Continuation.returnType = 123;
43
function Child({bar}) {
44
ops.push(['Child', bar]);
47
- return ReactCoroutine.createYield({
45
+ return ReactCallReturn.unstable_createReturn({
46
props: {
47
bar: bar,
48
},
@@ -57,20 +55,20 @@ describe('ReactCoroutine', () => {
55
return [<Child key="a" bar={true} />, <Child key="b" bar={false} />];
56
}
57
60
- function HandleYields(props, yields) {
61
- ops.push('HandleYields');
62
- return yields.map((y, i) => (
58
+ function HandleReturns(props, returns) {
59
+ ops.push('HandleReturns');
60
+ return returns.map((y, i) => (
61
<y.continuation key={i} isSame={props.foo === y.props.bar} />
62
));
63
}
64
65
// An alternative API could mark Parent as something that needs
68
- // yielding. E.g. Parent.handler = HandleYields;
66
+ // returning. E.g. Parent.handler = HandleReturns;
67
function Parent(props) {
68
ops.push('Parent');
71
- return ReactCoroutine.createCoroutine(
69
+ return ReactCallReturn.unstable_createCall(
70
props.children,
73
- HandleYields,
71
+ HandleReturns,
72
props,
73
);
74
}
@@ -86,11 +84,11 @@ describe('ReactCoroutine', () => {
84
'Parent',
85
'Indirection',
86
['Child', true],
89
- // Yield
87
+ // Return
88
['Child', false],
91
- // Yield
92
- 'HandleYields',
93
- // Continue yields
89
+ // Return
90
+ 'HandleReturns',
91
+ // Call continuations
92
['Continuation', true],
93
['Continuation', false],
94
]);
@@ -99,13 +97,13 @@ describe('ReactCoroutine', () => {
97
]);
98
});
99
102
- it('should update a coroutine', () => {
100
+ it('should update a call', () => {
101
function Continuation({isSame}) {
102
return <span prop={isSame ? 'foo==bar' : 'foo!=bar'} />;
103
}
104
105
function Child({bar}) {
108
- return ReactCoroutine.createYield({
106
+ return ReactCallReturn.unstable_createReturn({
107
props: {
108
bar: bar,
109
},
@@ -117,16 +115,16 @@ describe('ReactCoroutine', () => {
115
return [<Child key="a" bar={true} />, <Child key="b" bar={false} />];
116
}
117
120
- function HandleYields(props, yields) {
121
- return yields.map((y, i) => (
118
+ function HandleReturns(props, returns) {
119
+ return returns.map((y, i) => (
120
<y.continuation key={i} isSame={props.foo === y.props.bar} />
121
));
122
}
123
124
function Parent(props) {
127
- return ReactCoroutine.createCoroutine(
125
+ return ReactCallReturn.unstable_createCall(
126
props.children,
129
- HandleYields,
127
+ HandleReturns,
128
props,
129
);
130
}
@@ -148,7 +146,7 @@ describe('ReactCoroutine', () => {
146
]);
147
});
148
151
- it('should unmount a composite in a coroutine', () => {
149
+ it('should unmount a composite in a call', () => {
150
var ops = [];
151
152
class Continuation extends React.Component {
@@ -164,16 +162,16 @@ describe('ReactCoroutine', () => {
162
class Child extends React.Component {
163
render() {
164
ops.push('Child');
167
- return ReactCoroutine.createYield(Continuation);
165
+ return ReactCallReturn.unstable_createReturn(Continuation);
166
}
167
componentWillUnmount() {
168
ops.push('Unmount Child');
169
}
170
}
171
174
- function HandleYields(props, yields) {
175
- ops.push('HandleYields');
176
- return yields.map((ContinuationComponent, i) => (
172
+ function HandleReturns(props, returns) {
173
+ ops.push('HandleReturns');
174
+ return returns.map((ContinuationComponent, i) => (
175
<ContinuationComponent key={i} />
176
));
177
}
@@ -181,9 +179,9 @@ describe('ReactCoroutine', () => {
179
class Parent extends React.Component {
180
render() {
181
ops.push('Parent');
184
- return ReactCoroutine.createCoroutine(
182
+ return ReactCallReturn.unstable_createCall(
183
this.props.children,
186
- HandleYields,
184
+ HandleReturns,
185
this.props,
186
);
187
}
@@ -195,7 +193,7 @@ describe('ReactCoroutine', () => {
193
ReactNoop.render(<Parent><Child /></Parent>);
194
ReactNoop.flush();
195
198
- expect(ops).toEqual(['Parent', 'Child', 'HandleYields', 'Continuation']);
196
+ expect(ops).toEqual(['Parent', 'Child', 'HandleReturns', 'Continuation']);
197
198
ops = [];
199
@@ -209,25 +207,25 @@ describe('ReactCoroutine', () => {
207
]);
208
});
209
212
- it('should handle deep updates in coroutine', () => {
210
+ it('should handle deep updates in call', () => {
211
let instances = {};
212
213
class Counter extends React.Component {
214
state = {value: 5};
215
render() {
216
instances[this.props.id] = this;
219
- return ReactCoroutine.createYield(this.state.value);
217
+ return ReactCallReturn.unstable_createReturn(this.state.value);
218
}
219
}
220
221
function App(props) {
224
- return ReactCoroutine.createCoroutine(
222
+ return ReactCallReturn.unstable_createCall(
223
[
224
<Counter key="a" id="a" />,
225
<Counter key="b" id="b" />,
226
<Counter key="c" id="c" />,
227
],
230
- (p, yields) => yields.map((y, i) => <span key={i} prop={y * 100} />),
228
+ (p, returns) => returns.map((y, i) => <span key={i} prop={y * 100} />),
229
{},
230
);
231
}
packages/react-reconciler/src/ReactChildFiber.js
+71
-63
@@ -10,7 +10,7 @@
10
'use strict';
11
12
import type {ReactElement} from 'shared/ReactElementType';
13
-import type {ReactCoroutine, ReactPortal, ReactYield} from 'shared/ReactTypes';
13
+import type {ReactCall, ReactPortal, ReactReturn} from 'shared/ReactTypes';
14
import type {Fiber} from 'react-reconciler/src/ReactFiber';
15
import type {
16
ExpirationTime,
@@ -20,8 +20,6 @@ var ReactTypeOfSideEffect = require('shared/ReactTypeOfSideEffect');
20
var ReactTypeOfWork = require('shared/ReactTypeOfWork');
21
var emptyObject = require('fbjs/lib/emptyObject');
22
var invariant = require('fbjs/lib/invariant');
23
-
24
-var {REACT_COROUTINE_TYPE, REACT_YIELD_TYPE} = require('./ReactCoroutine');
23
var {REACT_PORTAL_TYPE} = require('./ReactPortal');
24
var ReactFiber = require('./ReactFiber');
25
@@ -78,8 +76,8 @@ const {
76
createFiberFromElement,
77
createFiberFromFragment,
78
createFiberFromText,
81
- createFiberFromCoroutine,
82
- createFiberFromYield,
79
+ createFiberFromCall,
80
+ createFiberFromReturn,
81
createFiberFromPortal,
82
} = ReactFiber;
83
@@ -90,8 +88,8 @@ const {
88
ClassComponent,
89
HostText,
90
HostPortal,
93
- CoroutineComponent,
94
- YieldComponent,
91
+ CallComponent,
92
+ ReturnComponent,
93
Fragment,
94
} = ReactTypeOfWork;
95
@@ -99,11 +97,21 @@ const {NoEffect, Placement, Deletion} = ReactTypeOfSideEffect;
97
98
const ITERATOR_SYMBOL = typeof Symbol === 'function' && Symbol.iterator;
99
const FAUX_ITERATOR_SYMBOL = '@@iterator'; // Before Symbol spec.
102
-// The Symbol used to tag the ReactElement type. If there is no native Symbol
100
+
101
+// The Symbol used to tag the ReactElement-like types. If there is no native Symbol
102
// nor polyfill, then a plain number is used for performance.
104
-const REACT_ELEMENT_TYPE =
105
- (typeof Symbol === 'function' && Symbol.for && Symbol.for('react.element')) ||
106
- 0xeac7;
103
+var REACT_ELEMENT_TYPE;
104
+var REACT_CALL_TYPE;
105
+var REACT_RETURN_TYPE;
106
+if (typeof Symbol === 'function' && Symbol.for) {
107
+ REACT_ELEMENT_TYPE = Symbol.for('react.element');
108
+ REACT_CALL_TYPE = Symbol.for('react.call');
109
+ REACT_RETURN_TYPE = Symbol.for('react.return');
110
+} else {
111
+ REACT_ELEMENT_TYPE = 0xeac7;
112
+ REACT_CALL_TYPE = 0xeac8;
113
+ REACT_RETURN_TYPE = 0xeac9;
114
+}
115
116
function getIteratorFn(maybeIterable: ?any): ?() => ?Iterator<*> {
117
if (maybeIterable === null || typeof maybeIterable === 'undefined') {
@@ -401,17 +409,17 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
409
}
410
}
411
404
- function updateCoroutine(
412
+ function updateCall(
413
returnFiber: Fiber,
414
current: Fiber | null,
407
- coroutine: ReactCoroutine,
415
+ call: ReactCall,
416
expirationTime: ExpirationTime,
417
): Fiber {
418
// TODO: Should this also compare handler to determine whether to reuse?
411
- if (current === null || current.tag !== CoroutineComponent) {
419
+ if (current === null || current.tag !== CallComponent) {
420
// Insert
413
- const created = createFiberFromCoroutine(
414
- coroutine,
421
+ const created = createFiberFromCall(
422
+ call,
423
returnFiber.internalContextTag,
424
expirationTime,
425
);
@@ -420,32 +428,32 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
428
} else {
429
// Move based on index
430
const existing = useFiber(current, expirationTime);
423
- existing.pendingProps = coroutine;
431
+ existing.pendingProps = call;
432
existing.return = returnFiber;
433
return existing;
434
}
435
}
436
429
- function updateYield(
437
+ function updateReturn(
438
returnFiber: Fiber,
439
current: Fiber | null,
432
- yieldNode: ReactYield,
440
+ returnNode: ReactReturn,
441
expirationTime: ExpirationTime,
442
): Fiber {
435
- if (current === null || current.tag !== YieldComponent) {
443
+ if (current === null || current.tag !== ReturnComponent) {
444
// Insert
437
- const created = createFiberFromYield(
438
- yieldNode,
445
+ const created = createFiberFromReturn(
446
+ returnNode,
447
returnFiber.internalContextTag,
448
expirationTime,
449
);
442
- created.type = yieldNode.value;
450
+ created.type = returnNode.value;
451
created.return = returnFiber;
452
return created;
453
} else {
454
// Move based on index
455
const existing = useFiber(current, expirationTime);
448
- existing.type = yieldNode.value;
456
+ existing.type = returnNode.value;
457
existing.return = returnFiber;
458
return existing;
459
}
@@ -535,8 +543,8 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
543
return created;
544
}
545
538
- case REACT_COROUTINE_TYPE: {
539
- const created = createFiberFromCoroutine(
546
+ case REACT_CALL_TYPE: {
547
+ const created = createFiberFromCall(
548
newChild,
549
returnFiber.internalContextTag,
550
expirationTime,
@@ -545,8 +553,8 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
553
return created;
554
}
555
548
- case REACT_YIELD_TYPE: {
549
- const created = createFiberFromYield(
556
+ case REACT_RETURN_TYPE: {
557
+ const created = createFiberFromReturn(
558
newChild,
559
returnFiber.internalContextTag,
560
expirationTime,
@@ -629,25 +637,25 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
637
}
638
}
639
632
- case REACT_COROUTINE_TYPE: {
640
+ case REACT_CALL_TYPE: {
641
if (newChild.key === key) {
634
- return updateCoroutine(
635
- returnFiber,
636
- oldFiber,
637
- newChild,
638
- expirationTime,
639
- );
642
+ return updateCall(returnFiber, oldFiber, newChild, expirationTime);
643
} else {
644
return null;
645
}
646
}
647
645
- case REACT_YIELD_TYPE: {
646
- // Yields don't have keys. If the previous node is implicitly keyed
648
+ case REACT_RETURN_TYPE: {
649
+ // Returns don't have keys. If the previous node is implicitly keyed
650
// we can continue to replace it without aborting even if it is not a
651
// yield.
652
if (key === null) {
650
- return updateYield(returnFiber, oldFiber, newChild, expirationTime);
653
+ return updateReturn(
654
+ returnFiber,
655
+ oldFiber,
656
+ newChild,
657
+ expirationTime,
658
+ );
659
} else {
660
return null;
661
}
@@ -722,12 +730,12 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
730
);
731
}
732
725
- case REACT_COROUTINE_TYPE: {
733
+ case REACT_CALL_TYPE: {
734
const matchedFiber =
735
existingChildren.get(
736
newChild.key === null ? newIdx : newChild.key,
737
) || null;
730
- return updateCoroutine(
738
+ return updateCall(
739
returnFiber,
740
matchedFiber,
741
newChild,
@@ -735,11 +743,11 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
743
);
744
}
745
738
- case REACT_YIELD_TYPE: {
739
- // Yields don't have keys, so we neither have to check the old nor
740
- // new node for the key. If both are yields, they match.
746
+ case REACT_RETURN_TYPE: {
747
+ // Returns don't have keys, so we neither have to check the old nor
748
+ // new node for the key. If both are returns, they match.
749
const matchedFiber = existingChildren.get(newIdx) || null;
742
- return updateYield(
750
+ return updateReturn(
751
returnFiber,
752
matchedFiber,
753
newChild,
@@ -796,7 +804,7 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
804
}
805
switch (child.$$typeof) {
806
case REACT_ELEMENT_TYPE:
799
- case REACT_COROUTINE_TYPE:
807
+ case REACT_CALL_TYPE:
808
case REACT_PORTAL_TYPE:
809
warnForMissingKey(child);
810
const key = child.key;
@@ -1236,22 +1244,22 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
1244
return created;
1245
}
1246
1239
- function reconcileSingleCoroutine(
1247
+ function reconcileSingleCall(
1248
returnFiber: Fiber,
1249
currentFirstChild: Fiber | null,
1242
- coroutine: ReactCoroutine,
1250
+ call: ReactCall,
1251
expirationTime: ExpirationTime,
1252
): Fiber {
1245
- const key = coroutine.key;
1253
+ const key = call.key;
1254
let child = currentFirstChild;
1255
while (child !== null) {
1256
// TODO: If key === null and child.key === null, then this only applies to
1257
// the first item in the list.
1258
if (child.key === key) {
1251
- if (child.tag === CoroutineComponent) {
1259
+ if (child.tag === CallComponent) {
1260
deleteRemainingChildren(returnFiber, child.sibling);
1261
const existing = useFiber(child, expirationTime);
1254
- existing.pendingProps = coroutine;
1262
+ existing.pendingProps = call;
1263
existing.return = returnFiber;
1264
return existing;
1265
} else {
@@ -1264,8 +1272,8 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
1272
child = child.sibling;
1273
}
1274
1267
- const created = createFiberFromCoroutine(
1268
- coroutine,
1275
+ const created = createFiberFromCall(
1276
+ call,
1277
returnFiber.internalContextTag,
1278
expirationTime,
1279
);
@@ -1273,19 +1281,19 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
1281
return created;
1282
}
1283
1276
- function reconcileSingleYield(
1284
+ function reconcileSingleReturn(
1285
returnFiber: Fiber,
1286
currentFirstChild: Fiber | null,
1279
- yieldNode: ReactYield,
1287
+ returnNode: ReactReturn,
1288
expirationTime: ExpirationTime,
1289
): Fiber {
1290
// There's no need to check for keys on yields since they're stateless.
1291
let child = currentFirstChild;
1292
if (child !== null) {
1285
- if (child.tag === YieldComponent) {
1293
+ if (child.tag === ReturnComponent) {
1294
deleteRemainingChildren(returnFiber, child.sibling);
1295
const existing = useFiber(child, expirationTime);
1288
- existing.type = yieldNode.value;
1296
+ existing.type = returnNode.value;
1297
existing.return = returnFiber;
1298
return existing;
1299
} else {
@@ -1293,12 +1301,12 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
1301
}
1302
}
1303
1296
- const created = createFiberFromYield(
1297
- yieldNode,
1304
+ const created = createFiberFromReturn(
1305
+ returnNode,
1306
returnFiber.internalContextTag,
1307
expirationTime,
1308
);
1301
- created.type = yieldNode.value;
1309
+ created.type = returnNode.value;
1310
created.return = returnFiber;
1311
return created;
1312
}
@@ -1372,18 +1380,18 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
1380
),
1381
);
1382
1375
- case REACT_COROUTINE_TYPE:
1383
+ case REACT_CALL_TYPE:
1384
return placeSingleChild(
1377
- reconcileSingleCoroutine(
1385
+ reconcileSingleCall(
1386
returnFiber,
1387
currentFirstChild,
1388
newChild,
1389
expirationTime,
1390
),
1391
);
1384
- case REACT_YIELD_TYPE:
1392
+ case REACT_RETURN_TYPE:
1393
return placeSingleChild(
1386
- reconcileSingleYield(
1394
+ reconcileSingleReturn(
1395
returnFiber,
1396
currentFirstChild,
1397
newChild,
packages/react-reconciler/src/ReactCoroutine.js
deleted
-98
@@ -1,98 +0,0 @@
1
-/**
2
- * Copyright (c) 2014-present, Facebook, Inc.
3
- *
4
- * This source code is licensed under the MIT license found in the
5
- * LICENSE file in the root directory of this source tree.
6
- *
7
- * @flow
8
- */
9
-
10
-'use strict';
11
-
12
-import type {
13
- ReactCoroutine,
14
- ReactNodeList,
15
- ReactYield,
16
-} from 'shared/ReactTypes';
17
-
18
-// The Symbol used to tag the special React types. If there is no native Symbol
19
-// nor polyfill, then a plain number is used for performance.
20
-var REACT_COROUTINE_TYPE;
21
-var REACT_YIELD_TYPE;
22
-if (typeof Symbol === 'function' && Symbol.for) {
23
- REACT_COROUTINE_TYPE = Symbol.for('react.coroutine');
24
- REACT_YIELD_TYPE = Symbol.for('react.yield');
25
-} else {
26
- REACT_COROUTINE_TYPE = 0xeac8;
27
- REACT_YIELD_TYPE = 0xeac9;
28
-}
29
-
30
-type CoroutineHandler<T> = (props: T, yields: Array<mixed>) => ReactNodeList;
31
-
32
-exports.createCoroutine = function<T>(
33
- children: mixed,
34
- handler: CoroutineHandler<T>,
35
- props: T,
36
- key: ?string = null,
37
-): ReactCoroutine {
38
- var coroutine = {
39
- // This tag allow us to uniquely identify this as a React Coroutine
40
- $$typeof: REACT_COROUTINE_TYPE,
41
- key: key == null ? null : '' + key,
42
- children: children,
43
- handler: handler,
44
- props: props,
45
- };
46
-
47
- if (__DEV__) {
48
- // TODO: Add _store property for marking this as validated.
49
- if (Object.freeze) {
50
- Object.freeze(coroutine.props);
51
- Object.freeze(coroutine);
52
- }
53
- }
54
-
55
- return coroutine;
56
-};
57
-
58
-exports.createYield = function(value: mixed): ReactYield {
59
- var yieldNode = {
60
- // This tag allow us to uniquely identify this as a React Yield
61
- $$typeof: REACT_YIELD_TYPE,
62
- value: value,
63
- };
64
-
65
- if (__DEV__) {
66
- // TODO: Add _store property for marking this as validated.
67
- if (Object.freeze) {
68
- Object.freeze(yieldNode);
69
- }
70
- }
71
-
72
- return yieldNode;
73
-};
74
-
75
-/**
76
- * Verifies the object is a coroutine object.
77
- */
78
-exports.isCoroutine = function(object: mixed): boolean {
79
- return (
80
- typeof object === 'object' &&
81
- object !== null &&
82
- object.$$typeof === REACT_COROUTINE_TYPE
83
- );
84
-};
85
-
86
-/**
87
- * Verifies the object is a yield object.
88
- */
89
-exports.isYield = function(object: mixed): boolean {
90
- return (
91
- typeof object === 'object' &&
92
- object !== null &&
93
- object.$$typeof === REACT_YIELD_TYPE
94
- );
95
-};
96
-
97
-exports.REACT_YIELD_TYPE = REACT_YIELD_TYPE;
98
-exports.REACT_COROUTINE_TYPE = REACT_COROUTINE_TYPE;
packages/react-reconciler/src/ReactDebugFiberPerf.js
+2
-2
@@ -29,7 +29,7 @@ if (__DEV__) {
29
HostComponent,
30
HostText,
31
HostPortal,
32
- YieldComponent,
32
+ ReturnComponent,
33
Fragment,
34
} = require('shared/ReactTypeOfWork');
35
@@ -168,7 +168,7 @@ if (__DEV__) {
168
case HostComponent:
169
case HostText:
170
case HostPortal:
171
- case YieldComponent:
171
+ case ReturnComponent:
172
case Fragment:
173
return true;
174
default:
packages/react-reconciler/src/ReactFiber.js
+12
-16
@@ -11,10 +11,10 @@
11
12
import type {ReactElement, Source} from 'shared/ReactElementType';
13
import type {
14
- ReactCoroutine,
14
+ ReactCall,
15
ReactFragment,
16
ReactPortal,
17
- ReactYield,
17
+ ReactReturn,
18
} from 'shared/ReactTypes';
19
import type {TypeOfWork} from 'shared/ReactTypeOfWork';
20
import type {TypeOfInternalContext} from './ReactTypeOfInternalContext';
@@ -31,8 +31,8 @@ var {
31
HostComponent,
32
HostText,
33
HostPortal,
34
- CoroutineComponent,
35
- YieldComponent,
34
+ CallComponent,
35
+ ReturnComponent,
36
Fragment,
37
} = require('shared/ReactTypeOfWork');
38
@@ -406,28 +406,24 @@ exports.createFiberFromHostInstanceForDeletion = function(): Fiber {
406
return fiber;
407
};
408
409
-exports.createFiberFromCoroutine = function(
410
- coroutine: ReactCoroutine,
409
+exports.createFiberFromCall = function(
410
+ call: ReactCall,
411
internalContextTag: TypeOfInternalContext,
412
expirationTime: ExpirationTime,
413
): Fiber {
414
- const fiber = createFiber(
415
- CoroutineComponent,
416
- coroutine.key,
417
- internalContextTag,
418
- );
419
- fiber.type = coroutine.handler;
420
- fiber.pendingProps = coroutine;
414
+ const fiber = createFiber(CallComponent, call.key, internalContextTag);
415
+ fiber.type = call.handler;
416
+ fiber.pendingProps = call;
417
fiber.expirationTime = expirationTime;
418
return fiber;
419
};
420
425
-exports.createFiberFromYield = function(
426
- yieldNode: ReactYield,
421
+exports.createFiberFromReturn = function(
422
+ returnNode: ReactReturn,
423
internalContextTag: TypeOfInternalContext,
424
expirationTime: ExpirationTime,
425
): Fiber {
430
- const fiber = createFiber(YieldComponent, null, internalContextTag);
426
+ const fiber = createFiber(ReturnComponent, null, internalContextTag);
427
fiber.expirationTime = expirationTime;
428
return fiber;
429
};
packages/react-reconciler/src/ReactFiberBeginWork.js
+19
-26
@@ -10,7 +10,7 @@
10
'use strict';
11
12
import type {HostConfig} from 'react-reconciler';
13
-import type {ReactCoroutine} from 'shared/ReactTypes';
13
+import type {ReactCall} from 'shared/ReactTypes';
14
import type {Fiber} from 'react-reconciler/src/ReactFiber';
15
import type {HostContext} from './ReactFiberHostContext';
16
import type {HydrationContext} from './ReactFiberHydrationContext';
@@ -25,9 +25,9 @@ var {
25
HostComponent,
26
HostText,
27
HostPortal,
28
- CoroutineComponent,
29
- CoroutineHandlerPhase,
30
- YieldComponent,
28
+ CallComponent,
29
+ CallHandlerPhase,
30
+ ReturnComponent,
31
Fragment,
32
} = require('shared/ReactTypeOfWork');
33
var {
@@ -537,34 +537,27 @@ module.exports = function<T, P, I, TI, PI, C, CC, CX, PL>(
537
}
538
}
539
540
- function updateCoroutineComponent(
541
- current,
542
- workInProgress,
543
- renderExpirationTime,
544
- ) {
545
- var nextCoroutine = (workInProgress.pendingProps: null | ReactCoroutine);
540
+ function updateCallComponent(current, workInProgress, renderExpirationTime) {
541
+ var nextCall = (workInProgress.pendingProps: null | ReactCall);
542
if (hasContextChanged()) {
543
// Normally we can bail out on props equality but if context has changed
544
// we don't do the bailout and we have to reuse existing props instead.
549
- if (nextCoroutine === null) {
550
- nextCoroutine = current && current.memoizedProps;
545
+ if (nextCall === null) {
546
+ nextCall = current && current.memoizedProps;
547
invariant(
552
- nextCoroutine !== null,
548
+ nextCall !== null,
549
'We should always have pending or current props. This error is ' +
550
'likely caused by a bug in React. Please file an issue.',
551
);
552
}
557
- } else if (
558
- nextCoroutine === null ||
559
- workInProgress.memoizedProps === nextCoroutine
560
- ) {
561
- nextCoroutine = workInProgress.memoizedProps;
553
+ } else if (nextCall === null || workInProgress.memoizedProps === nextCall) {
554
+ nextCall = workInProgress.memoizedProps;
555
// TODO: When bailing out, we might need to return the stateNode instead
556
// of the child. To check it for work.
557
// return bailoutOnAlreadyFinishedWork(current, workInProgress);
558
}
559
567
- const nextChildren = nextCoroutine.children;
560
+ const nextChildren = nextCall.children;
561
562
// The following is a fork of reconcileChildrenAtExpirationTime but using
563
// stateNode to store the child.
@@ -591,7 +584,7 @@ module.exports = function<T, P, I, TI, PI, C, CC, CX, PL>(
584
);
585
}
586
594
- memoizeProps(workInProgress, nextCoroutine);
587
+ memoizeProps(workInProgress, nextCall);
588
// This doesn't take arbitrary time so we could synchronously just begin
589
// eagerly do the work of workInProgress.child as an optimization.
590
return workInProgress.stateNode;
@@ -761,18 +754,18 @@ module.exports = function<T, P, I, TI, PI, C, CC, CX, PL>(
754
);
755
case HostText:
756
return updateHostText(current, workInProgress);
764
- case CoroutineHandlerPhase:
757
+ case CallHandlerPhase:
758
// This is a restart. Reset the tag to the initial phase.
766
- workInProgress.tag = CoroutineComponent;
759
+ workInProgress.tag = CallComponent;
760
// Intentionally fall through since this is now the same.
768
- case CoroutineComponent:
769
- return updateCoroutineComponent(
761
+ case CallComponent:
762
+ return updateCallComponent(
763
current,
764
workInProgress,
765
renderExpirationTime,
766
);
774
- case YieldComponent:
775
- // A yield component is just a placeholder, we can just run through the
767
+ case ReturnComponent:
768
+ // A return component is just a placeholder, we can just run through the
769
// next one immediately.
770
return null;
771
case HostPortal:
packages/react-reconciler/src/ReactFiberCommitWork.js
+2
-2
@@ -20,7 +20,7 @@ var {
20
HostComponent,
21
HostText,
22
HostPortal,
23
- CoroutineComponent,
23
+ CallComponent,
24
} = ReactTypeOfWork;
25
var {
26
invokeGuardedCallback,
@@ -217,7 +217,7 @@ module.exports = function<T, P, I, TI, PI, C, CC, CX, PL>(
217
safelyDetachRef(current);
218
return;
219
}
220
- case CoroutineComponent: {
220
+ case CallComponent: {
221
commitNestedUnmounts(current.stateNode);
222
return;
223
}
packages/react-reconciler/src/ReactFiberCompleteWork.js
+26
-26
@@ -10,7 +10,7 @@
10
'use strict';
11
12
import type {HostConfig} from 'react-reconciler';
13
-import type {ReactCoroutine} from 'shared/ReactTypes';
13
+import type {ReactCall} from 'shared/ReactTypes';
14
import type {Fiber} from './ReactFiber';
15
import type {ExpirationTime} from './ReactFiberExpirationTime';
16
import type {HostContext} from './ReactFiberHostContext';
@@ -26,9 +26,9 @@ var {
26
HostComponent,
27
HostText,
28
HostPortal,
29
- CoroutineComponent,
30
- CoroutineHandlerPhase,
31
- YieldComponent,
29
+ CallComponent,
30
+ CallHandlerPhase,
31
+ ReturnComponent,
32
Fragment,
33
} = require('shared/ReactTypeOfWork');
34
var {Placement, Ref, Update} = require('shared/ReactTypeOfSideEffect');
@@ -79,7 +79,7 @@ module.exports = function<T, P, I, TI, PI, C, CC, CX, PL>(
79
workInProgress.effectTag |= Ref;
80
}
81
82
- function appendAllYields(yields: Array<mixed>, workInProgress: Fiber) {
82
+ function appendAllReturns(returns: Array<mixed>, workInProgress: Fiber) {
83
let node = workInProgress.stateNode;
84
if (node) {
85
node.return = workInProgress;
@@ -90,9 +90,9 @@ module.exports = function<T, P, I, TI, PI, C, CC, CX, PL>(
90
node.tag === HostText ||
91
node.tag === HostPortal
92
) {
93
- invariant(false, 'A coroutine cannot have host component children.');
94
- } else if (node.tag === YieldComponent) {
95
- yields.push(node.type);
93
+ invariant(false, 'A call cannot have host component children.');
94
+ } else if (node.tag === ReturnComponent) {
95
+ returns.push(node.type);
96
} else if (node.child !== null) {
97
node.child.return = node;
98
node = node.child;
@@ -109,34 +109,34 @@ module.exports = function<T, P, I, TI, PI, C, CC, CX, PL>(
109
}
110
}
111
112
- function moveCoroutineToHandlerPhase(
112
+ function moveCallToHandlerPhase(
113
current: Fiber | null,
114
workInProgress: Fiber,
115
renderExpirationTime: ExpirationTime,
116
) {
117
- var coroutine = (workInProgress.memoizedProps: ?ReactCoroutine);
117
+ var call = (workInProgress.memoizedProps: ?ReactCall);
118
invariant(
119
- coroutine,
119
+ call,
120
'Should be resolved by now. This error is likely caused by a bug in ' +
121
'React. Please file an issue.',
122
);
123
124
- // First step of the coroutine has completed. Now we need to do the second.
125
- // TODO: It would be nice to have a multi stage coroutine represented by a
124
+ // First step of the call has completed. Now we need to do the second.
125
+ // TODO: It would be nice to have a multi stage call represented by a
126
// single component, or at least tail call optimize nested ones. Currently
127
// that requires additional fields that we don't want to add to the fiber.
128
// So this requires nested handlers.
129
// Note: This doesn't mutate the alternate node. I don't think it needs to
130
// since this stage is reset for every pass.
131
- workInProgress.tag = CoroutineHandlerPhase;
131
+ workInProgress.tag = CallHandlerPhase;
132
133
- // Build up the yields.
133
+ // Build up the returns.
134
// TODO: Compare this to a generator or opaque helpers like Children.
135
- var yields: Array<mixed> = [];
136
- appendAllYields(yields, workInProgress);
137
- var fn = coroutine.handler;
138
- var props = coroutine.props;
139
- var nextChildren = fn(props, yields);
135
+ var returns: Array<mixed> = [];
136
+ appendAllReturns(returns, workInProgress);
137
+ var fn = call.handler;
138
+ var props = call.props;
139
+ var nextChildren = fn(props, returns);
140
141
var currentFirstChild = current !== null ? current.child : null;
142
workInProgress.child = reconcileChildFibers(
@@ -566,17 +566,17 @@ module.exports = function<T, P, I, TI, PI, C, CC, CX, PL>(
566
}
567
return null;
568
}
569
- case CoroutineComponent:
570
- return moveCoroutineToHandlerPhase(
569
+ case CallComponent:
570
+ return moveCallToHandlerPhase(
571
current,
572
workInProgress,
573
renderExpirationTime,
574
);
575
- case CoroutineHandlerPhase:
576
- // Reset the tag to now be a first phase coroutine.
577
- workInProgress.tag = CoroutineComponent;
575
+ case CallHandlerPhase:
576
+ // Reset the tag to now be a first phase call.
577
+ workInProgress.tag = CallComponent;
578
return null;
579
- case YieldComponent:
579
+ case ReturnComponent:
580
// Does nothing.
581
return null;
582
case Fragment:
packages/react-reconciler/src/ReactFiberScheduler.js
+1
-1
@@ -507,7 +507,7 @@ module.exports = function<T, P, I, TI, PI, C, CC, CX, PL>(
507
// Check for pending updates.
508
let newExpirationTime = getUpdateExpirationTime(workInProgress);
509
510
- // TODO: Coroutines need to visit stateNode
510
+ // TODO: Calls need to visit stateNode
511
512
// Bubble up the earliest expiration time.
513
let child = workInProgress.child;
packages/react-reconciler/src/__tests__/ReactIncrementalPerf-test.js
+8
-8
@@ -11,7 +11,7 @@
11
12
describe('ReactDebugFiberPerf', () => {
13
let React;
14
- let ReactCoroutine;
14
+ let ReactCallReturn;
15
let ReactNoop;
16
let ReactPortal;
17
let PropTypes;
@@ -115,8 +115,8 @@ describe('ReactDebugFiberPerf', () => {
115
// Import after the polyfill is set up:
116
React = require('react');
117
ReactNoop = require('react-noop-renderer');
118
+ ReactCallReturn = require('react-call-return');
119
// TODO: can we express this test with only public API?
119
- ReactCoroutine = require('react-reconciler/src/ReactCoroutine');
120
ReactPortal = require('react-reconciler/src/ReactPortal');
121
PropTypes = require('prop-types');
122
});
@@ -431,13 +431,13 @@ describe('ReactDebugFiberPerf', () => {
431
expect(getFlameChart()).toMatchSnapshot();
432
});
433
434
- it('supports coroutines', () => {
434
+ it('supports returns', () => {
435
function Continuation({isSame}) {
436
return <span prop={isSame ? 'foo==bar' : 'foo!=bar'} />;
437
}
438
439
function CoChild({bar}) {
440
- return ReactCoroutine.createYield({
440
+ return ReactCallReturn.unstable_createReturn({
441
props: {
442
bar: bar,
443
},
@@ -449,16 +449,16 @@ describe('ReactDebugFiberPerf', () => {
449
return [<CoChild key="a" bar={true} />, <CoChild key="b" bar={false} />];
450
}
451
452
- function HandleYields(props, yields) {
453
- return yields.map((y, i) => (
452
+ function HandleReturns(props, returns) {
453
+ return returns.map((y, i) => (
454
<y.continuation key={i} isSame={props.foo === y.props.bar} />
455
));
456
}
457
458
function CoParent(props) {
459
- return ReactCoroutine.createCoroutine(
459
+ return ReactCallReturn.unstable_createCall(
460
props.children,
461
- HandleYields,
461
+ HandleReturns,
462
props,
463
);
464
}
packages/react-reconciler/src/__tests__/__snapshots__/ReactIncrementalPerf-test.js.snap
+2
-2
@@ -243,11 +243,11 @@ exports[`ReactDebugFiberPerf skips parents during setState 1`] = `
243
"
244
`;
245
246
-exports[`ReactDebugFiberPerf supports coroutines 1`] = `
246
+exports[`ReactDebugFiberPerf supports returns 1`] = `
247
"⚛ (React Tree Reconciliation)
248
⚛ App [mount]
249
⚛ CoParent [mount]
250
- ⚛ HandleYields [mount]
250
+ ⚛ HandleReturns [mount]
251
⚛ Indirection [mount]
252
⚛ CoChild [mount]
253
⚛ CoChild [mount]
packages/shared/ReactTypeOfWork.js
+3
-3
@@ -19,8 +19,8 @@ module.exports = {
19
HostPortal: 4, // A subtree. Could be an entry point to a different renderer.
20
HostComponent: 5,
21
HostText: 6,
22
- CoroutineComponent: 7,
23
- CoroutineHandlerPhase: 8,
24
- YieldComponent: 9,
22
+ CallComponent: 7,
23
+ CallHandlerPhase: 8,
24
+ ReturnComponent: 9,
25
Fragment: 10,
26
};
packages/shared/ReactTypes.js
+6
-6
@@ -11,8 +11,8 @@
11
12
export type ReactNode =
13
| React$Element<any>
14
- | ReactCoroutine
15
- | ReactYield
14
+ | ReactCall
15
+ | ReactReturn
16
| ReactPortal
17
| ReactText
18
| ReactFragment;
@@ -25,16 +25,16 @@ export type ReactText = string | number;
25
26
export type ReactEmpty = null | void | boolean;
27
28
-export type ReactCoroutine = {
28
+export type ReactCall = {
29
$$typeof: Symbol | number,
30
key: null | string,
31
children: any,
32
- // This should be a more specific CoroutineHandler
33
- handler: (props: any, yields: Array<mixed>) => ReactNodeList,
32
+ // This should be a more specific CallHandler
33
+ handler: (props: any, returns: Array<mixed>) => ReactNodeList,
34
props: any,
35
};
36
37
-export type ReactYield = {
37
+export type ReactReturn = {
38
$$typeof: Symbol | number,
39
value: mixed,
40
};
scripts/rollup/bundles.js
+10
@@ -196,6 +196,16 @@ const bundles = [
196
global: 'ReactReconciler',
197
externals: ['react'],
198
},
199
+
200
+ /******* React Call Return (experimental) *******/
201
+ {
202
+ label: 'react-call-return',
203
+ bundleTypes: [NODE_DEV, NODE_PROD],
204
+ moduleType: ISOMORPHIC,
205
+ entry: 'react-call-return',
206
+ global: 'ReactCallReturn',
207
+ externals: [],
208
+ },
209
];
210
211
// Based on deep-freeze by substack (public domain)
scripts/rollup/results.json
+8
@@ -199,6 +199,14 @@
199
"react-reconciler.production.min.js (NODE_PROD)": {
200
"size": 38245,
201
"gzip": 11943
202
+ },
203
+ "react-call-return.development.js (NODE_DEV)": {
204
+ "size": 2721,
205
+ "gzip": 943
206
+ },
207
+ "react-call-return.production.min.js (NODE_PROD)": {
208
+ "size": 845,
209
+ "gzip": 484
210
}
211
}
212
}
\ No newline at end of file