@samitouri / QOS-React / commits / 2c0a8fb99e

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