Coroutines
Sebastian Markbage committed
May 26, 2016 at 18:13 UTC
fd4f74ef955b6d627af8a9fc55a61c828564a67e
12 files changed
+471
-90
src/isomorphic/classic/element/ReactElement.js
+2
@@ -398,4 +398,6 @@ ReactElement.isValidElement = function(object) {
398
);
399
};
400
401
+ReactElement.REACT_ELEMENT_TYPE = REACT_ELEMENT_TYPE;
402
+
403
module.exports = ReactElement;
src/isomorphic/classic/element/ReactElementValidator.js
+2
-1
@@ -184,7 +184,8 @@ function validatePropTypes(element) {
184
var ReactElementValidator = {
185
186
createElement: function(type, props, children) {
187
- var validType = typeof type === 'string' || typeof type === 'function';
187
+ var validType = typeof type === 'string' || typeof type === 'function' ||
188
+ (type !== null && typeof type === 'object');
189
// We warn in this case but don't throw. We expect the element creation to
190
// succeed and there will likely be errors in render.
191
warning(
src/renderers/noop/__tests__/ReactNoop-test.js
+50
-5
@@ -13,14 +13,17 @@
13
14
var React;
15
var ReactNoop;
16
+var ReactCoroutine;
17
18
describe('ReactComponent', function() {
19
beforeEach(function() {
20
React = require('React');
21
ReactNoop = require('ReactNoop');
22
+ ReactCoroutine = require('ReactCoroutine');
23
+ spyOn(console, 'log');
24
});
25
23
- /*it('should render a simple component', function() {
26
+ it('should render a simple component', function() {
27
28
function Bar() {
29
return <div>Hello World</div>;
@@ -33,7 +36,7 @@ describe('ReactComponent', function() {
36
ReactNoop.render(<Foo />);
37
ReactNoop.flush();
38
36
- });*/
39
+ });
40
41
it('should render a simple component, in steps if needed', function() {
42
@@ -49,12 +52,54 @@ describe('ReactComponent', function() {
52
}
53
54
ReactNoop.render(<Foo />);
52
- console.log('Nothing done');
55
+ // console.log('Nothing done');
56
ReactNoop.flushLowPri(7);
54
- console.log('Yield');
57
+ // console.log('Yield');
58
ReactNoop.flushLowPri(50);
56
- console.log('Done');
59
+ // console.log('Done');
60
});
61
62
+ it('should render a coroutine', function() {
63
+
64
+ function Continuation({ isSame }) {
65
+ return <span>{isSame ? 'foo==bar' : 'foo!=bar'}</span>;
66
+ }
67
+
68
+ // An alternative API could mark Continuation as something that needs
69
+ // yielding. E.g. Continuation.yieldType = 123;
70
+ function Child({ bar }) {
71
+ return ReactCoroutine.createYield({
72
+ bar: bar,
73
+ }, Continuation, null);
74
+ }
75
+
76
+ function Indirection() {
77
+ return [<Child bar={true} />, <Child bar={false} />];
78
+ }
79
+
80
+ function HandleYields(props, yields) {
81
+ return yields.map(y =>
82
+ <y.continuation isSame={props.foo === y.props.bar} />
83
+ );
84
+ }
85
+
86
+ // An alternative API could mark Parent as something that needs
87
+ // yielding. E.g. Parent.handler = HandleYields;
88
+ function Parent(props) {
89
+ return ReactCoroutine.createCoroutine(
90
+ props.children,
91
+ HandleYields,
92
+ props
93
+ );
94
+ }
95
+
96
+ function App() {
97
+ return <div><Parent foo={true}><Indirection /></Parent></div>;
98
+ }
99
+
100
+ ReactNoop.render(<App />);
101
+ ReactNoop.flush();
102
+
103
+ });
104
105
});
src/renderers/shared/fiber/ReactChildFiber.js
+70
-27
@@ -12,33 +12,53 @@
12
13
'use strict';
14
15
+import type { ReactCoroutine, ReactYield } from 'ReactCoroutine';
16
import type { Fiber } from 'ReactFiber';
17
17
-var ReactElement = require('ReactElement');
18
+import type { ReactNodeList } from 'ReactTypes';
19
19
-var ReactFiber = require('ReactFiber');
20
-
21
-type ReactNode = ReactElement | ReactFragment | ReactText;
22
-
23
-type ReactFragment = Iterable<ReactNode | ReactEmpty>;
24
-
25
-type ReactNodeList = ReactNode | ReactEmpty;
20
+var {
21
+ REACT_ELEMENT_TYPE,
22
+} = require('ReactElement');
23
+var {
24
+ REACT_COROUTINE_TYPE,
25
+ REACT_YIELD_TYPE,
26
+} = require('ReactCoroutine');
27
27
-type ReactText = string | number;
28
-
29
-type ReactEmpty = null | void | boolean;
28
+var ReactFiber = require('ReactFiber');
29
+var ReactReifiedYield = require('ReactReifiedYield');
30
31
function createSubsequentChild(parent : Fiber, previousSibling : Fiber, newChildren) : Fiber {
32
if (typeof newChildren !== 'object' || newChildren === null) {
33
return previousSibling;
34
}
35
36
- if (ReactElement.isValidElement(newChildren)) {
37
- var element = (newChildren : ReactElement);
38
- var child = ReactFiber.createFiberFromElement(element);
39
- previousSibling.sibling = child;
40
- child.parent = parent;
41
- return child;
36
+ switch (newChildren.$$typeof) {
37
+ case REACT_ELEMENT_TYPE: {
38
+ const element = (newChildren : ReactElement);
39
+ const child = ReactFiber.createFiberFromElement(element);
40
+ previousSibling.sibling = child;
41
+ child.parent = parent;
42
+ return child;
43
+ }
44
+
45
+ case REACT_COROUTINE_TYPE: {
46
+ const coroutine = (newChildren : ReactCoroutine);
47
+ const child = ReactFiber.createFiberFromCoroutine(coroutine);
48
+ previousSibling.sibling = child;
49
+ child.parent = parent;
50
+ return child;
51
+ }
52
+
53
+ case REACT_YIELD_TYPE: {
54
+ const yieldNode = (newChildren : ReactYield);
55
+ const reifiedYield = ReactReifiedYield.createReifiedYield(yieldNode);
56
+ const child = ReactFiber.createFiberFromYield(yieldNode);
57
+ child.output = reifiedYield;
58
+ previousSibling.sibling = child;
59
+ child.parent = parent;
60
+ return child;
61
+ }
62
}
63
64
if (Array.isArray(newChildren)) {
@@ -48,39 +68,62 @@ function createSubsequentChild(parent : Fiber, previousSibling : Fiber, newChild
68
}
69
return prev;
70
} else {
71
+ console.log('Unknown child', newChildren);
72
return previousSibling;
73
}
74
}
75
76
function createFirstChild(parent, newChildren) {
77
if (typeof newChildren !== 'object' || newChildren === null) {
57
- parent.child = null;
78
return null;
79
}
80
61
- if (ReactElement.isValidElement(newChildren)) {
62
- var element = (newChildren : ReactElement);
63
- var child = ReactFiber.createFiberFromElement(element);
64
- parent.child = child;
65
- child.parent = parent;
66
- return child;
81
+ switch (newChildren.$$typeof) {
82
+ case REACT_ELEMENT_TYPE: {
83
+ const element = (newChildren : ReactElement);
84
+ const child = ReactFiber.createFiberFromElement(element);
85
+ child.parent = parent;
86
+ return child;
87
+ }
88
+
89
+ case REACT_COROUTINE_TYPE: {
90
+ const coroutine = (newChildren : ReactCoroutine);
91
+ const child = ReactFiber.createFiberFromCoroutine(coroutine);
92
+ child.parent = parent;
93
+ return child;
94
+ }
95
+
96
+ case REACT_YIELD_TYPE: {
97
+ // A yield results in a fragment fiber whose output is the continuation.
98
+ // TODO: When there is only a single child, we can optimize this to avoid
99
+ // the fragment.
100
+ const yieldNode = (newChildren : ReactYield);
101
+ const reifiedYield = ReactReifiedYield.createReifiedYield(yieldNode);
102
+ const child = ReactFiber.createFiberFromYield(yieldNode);
103
+ child.output = reifiedYield;
104
+ child.parent = parent;
105
+ return child;
106
+ }
107
}
108
109
if (Array.isArray(newChildren)) {
110
+ var first : ?Fiber = null;
111
var prev : ?Fiber = null;
112
for (var i = 0; i < newChildren.length; i++) {
113
if (prev == null) {
114
prev = createFirstChild(parent, newChildren[i]);
115
+ first = prev;
116
} else {
117
prev = createSubsequentChild(parent, prev, newChildren[i]);
118
}
119
}
120
+ return first;
121
} else {
79
- parent.child = null;
122
+ console.log('Unknown child', newChildren);
123
return null;
124
}
125
}
126
84
-exports.reconcileChildFibers = function(parent : Fiber, newChildren : ReactNodeList) : void {
85
- createFirstChild(parent, newChildren);
127
+exports.reconcileChildFibers = function(parent : Fiber, firstChild : ?Fiber, newChildren : ReactNodeList) : ?Fiber {
128
+ return createFirstChild(parent, newChildren);
129
};
src/renderers/shared/fiber/ReactFiber.js
+53
-17
@@ -17,30 +17,42 @@ var {
17
IndeterminateComponent,
18
ClassComponent,
19
HostComponent,
20
+ CoroutineComponent,
21
+ YieldComponent,
22
} = ReactTypesOfWork;
23
22
-type StateNode = {};
23
-type EffectHandler = () => void;
24
-type EffectTag = number;
24
+var ReactElement = require('ReactElement');
25
+
26
+import type { ReactCoroutine, ReactYield } from 'ReactCoroutine';
27
28
export type Fiber = {
29
30
+ // Tag identifying the type of fiber.
31
tag: number,
32
30
- parent: ?Fiber,
33
+ // Singly Linked List Tree Structure.
34
+ parent: ?Fiber, // Consider a regenerated temporary parent stack instead.
35
child: ?Fiber,
36
sibling: ?Fiber,
37
34
- input: ?Object,
35
- output: ?Object,
38
+ // Input is the data coming into process this fiber. Arguments.
39
+ input: any, // This type will be more specific once we overload the tag.
40
+ // Output is the return value of this fiber, or a linked list of return values
41
+ // if this returns multiple values. Such as a fragment.
42
+ output: any, // This type will be more specific once we overload the tag.
43
+
44
+ // Used by multi-stage coroutines.
45
+ stage: number, // Consider reusing the tag field instead.
46
47
+ // This will be used to quickly determine if a subtree has no pending changes.
48
hasPendingChanges: bool,
49
39
- stateNode: StateNode,
50
+ // The local state associated with this fiber.
51
+ stateNode: ?Object,
52
53
};
54
43
-var createFiber = function(tag : number, handlerTag : number) : Fiber {
55
+var createFiber = function(tag : number) : Fiber {
56
return {
57
58
tag: tag,
@@ -52,9 +64,11 @@ var createFiber = function(tag : number, handlerTag : number) : Fiber {
64
input: null,
65
output: null,
66
67
+ stage: 0,
68
+
69
hasPendingChanges: true,
70
57
- stateNode: {},
71
+ stateNode: null,
72
73
};
74
};
@@ -64,17 +78,39 @@ function shouldConstruct(Component) {
78
}
79
80
exports.createFiberFromElement = function(element : ReactElement) {
81
+ const fiber = exports.createFiberFromElementType(element.type);
82
+ if (typeof element.type === 'object') {
83
+ // Hacky McHack
84
+ element = ReactElement(fiber.input, null, element.ref, null, null, null, element.props);
85
+ }
86
+ fiber.input = element;
87
+ return fiber;
88
+};
89
+
90
+exports.createFiberFromElementType = function(type : mixed) {
91
let fiber;
68
- if (typeof element.type === 'function') {
69
- fiber = shouldConstruct(element.type) ?
70
- createFiber(ClassComponent, 0) :
71
- createFiber(IndeterminateComponent, 0);
72
- } else if (typeof element.type === 'string') {
73
- fiber = createFiber(HostComponent, 1);
92
+ if (typeof type === 'function') {
93
+ fiber = shouldConstruct(type) ?
94
+ createFiber(ClassComponent) :
95
+ createFiber(IndeterminateComponent);
96
+ } else if (typeof type === 'string') {
97
+ fiber = createFiber(HostComponent);
98
+ } else if (typeof type === 'object' && type !== null) {
99
+ // Currently assumed to be a continuation and therefore is a fiber already.
100
+ fiber = type;
101
} else {
75
- throw new Error('Unknown component type: ' + typeof element.type);
102
+ throw new Error('Unknown component type: ' + typeof type);
103
}
104
+ return fiber;
105
+};
106
78
- fiber.input = element;
107
+exports.createFiberFromCoroutine = function(coroutine : ReactCoroutine) {
108
+ const fiber = createFiber(CoroutineComponent);
109
+ fiber.input = coroutine;
110
+ return fiber;
111
+};
112
+
113
+exports.createFiberFromYield = function(yieldNode : ReactYield) {
114
+ const fiber = createFiber(YieldComponent);
115
return fiber;
116
};
src/renderers/shared/fiber/ReactFiberBeginWork.js
+44
-7
@@ -12,6 +12,7 @@
12
13
'use strict';
14
15
+import type { ReactCoroutine } from 'ReactCoroutine';
16
import type { Fiber } from 'ReactFiber';
17
18
var ReactChildFiber = require('ReactChildFiber');
@@ -21,6 +22,8 @@ var {
22
FunctionalComponent,
23
ClassComponent,
24
HostComponent,
25
+ CoroutineComponent,
26
+ YieldComponent,
27
} = ReactTypesOfWork;
28
29
function getElement(unitOfWork) : ReactElement {
@@ -38,19 +41,21 @@ function updateFunctionalComponent(unitOfWork) {
41
console.log('perform work on:', fn.name);
42
var nextChildren = fn(props);
43
41
- ReactChildFiber.reconcileChildFibers(
44
+ unitOfWork.child = ReactChildFiber.reconcileChildFibers(
45
unitOfWork,
46
+ unitOfWork.child,
47
nextChildren
48
);
49
}
50
51
function updateHostComponent(unitOfWork) {
52
var element = getElement(unitOfWork);
49
- console.log('host component', element.type);
53
+ console.log('host component', element.type, typeof element.props.children === 'string' ? element.props.children : '');
54
55
var nextChildren = element.props.children;
52
- ReactChildFiber.reconcileChildFibers(
56
+ unitOfWork.child = ReactChildFiber.reconcileChildFibers(
57
unitOfWork,
58
+ unitOfWork.child,
59
nextChildren
60
);
61
}
@@ -69,13 +74,27 @@ function mountIndeterminateComponent(unitOfWork) {
74
// Proceed under the assumption that this is a functional component
75
unitOfWork.tag = FunctionalComponent;
76
}
72
- ReactChildFiber.reconcileChildFibers(
77
+ unitOfWork.child = ReactChildFiber.reconcileChildFibers(
78
unitOfWork,
79
+ unitOfWork.child,
80
value
81
);
82
}
83
78
-exports.beginWork = function(unitOfWork : Fiber) : ?Fiber {
84
+function updateCoroutineComponent(unitOfWork) {
85
+ var coroutine = (unitOfWork.input : ?ReactCoroutine);
86
+ if (!coroutine) {
87
+ throw new Error('Should be resolved by now');
88
+ }
89
+ console.log('begin coroutine', coroutine.handler.name);
90
+ unitOfWork.child = ReactChildFiber.reconcileChildFibers(
91
+ unitOfWork,
92
+ unitOfWork.child,
93
+ coroutine.children
94
+ );
95
+}
96
+
97
+function beginWork(unitOfWork : Fiber) : ?Fiber {
98
switch (unitOfWork.tag) {
99
case IndeterminateComponent:
100
mountIndeterminateComponent(unitOfWork);
@@ -84,14 +103,32 @@ exports.beginWork = function(unitOfWork : Fiber) : ?Fiber {
103
updateFunctionalComponent(unitOfWork);
104
break;
105
case ClassComponent:
87
- // $FlowFixMe
106
console.log('class component', unitOfWork.input.type.name);
107
break;
108
case HostComponent:
109
updateHostComponent(unitOfWork);
110
break;
111
+ case CoroutineComponent:
112
+ // Reset the stage to zero.
113
+ unitOfWork.stage = 0;
114
+ updateCoroutineComponent(unitOfWork);
115
+ // This doesn't take arbitrary time so we could synchronously just begin
116
+ // eagerly do the work of unitOfWork.child as an optimization.
117
+ if (unitOfWork.child) {
118
+ return beginWork(unitOfWork.child);
119
+ }
120
+ break;
121
+ case YieldComponent:
122
+ // A yield component is just a placeholder, we can just run through the
123
+ // next one immediately.
124
+ if (unitOfWork.sibling) {
125
+ return beginWork(unitOfWork.sibling);
126
+ }
127
+ return null;
128
default:
129
throw new Error('Unknown unit of work tag');
130
}
131
return unitOfWork.child;
97
-};
132
+}
133
+
134
+exports.beginWork = beginWork;
src/renderers/shared/fiber/ReactFiberCompleteWork.js
+78
-3
@@ -12,8 +12,11 @@
12
13
'use strict';
14
15
+import type { ReactCoroutine } from 'ReactCoroutine';
16
import type { Fiber } from 'ReactFiber';
17
18
+import type { ReifiedYield } from 'ReactReifiedYield';
19
+
20
var ReactChildFiber = require('ReactChildFiber');
21
var ReactTypesOfWork = require('ReactTypesOfWork');
22
var {
@@ -21,22 +24,94 @@ var {
24
FunctionalComponent,
25
ClassComponent,
26
HostComponent,
27
+ CoroutineComponent,
28
+ YieldComponent,
29
} = ReactTypesOfWork;
30
31
+function transferOutput(child : ?Fiber, parent : Fiber) {
32
+ // If we have a single result, we just pass that through as the output to
33
+ // avoid unnecessary traversal. When we have multiple output, we just pass
34
+ // the linked list of fibers that has the individual output values.
35
+ parent.output = (child && !child.sibling) ? child.output : child;
36
+}
37
+
38
+function recursivelyFillYields(yields, output : ?Fiber | ?ReifiedYield) {
39
+ if (!output) {
40
+ // Ignore nulls etc.
41
+ } else if (output.tag !== undefined) { // TODO: Fix this fragile duck test.
42
+ // Detect if this is a fiber, if so it is a fragment result.
43
+ // $FlowFixMe: Refinement issue.
44
+ var item = (output : Fiber);
45
+ do {
46
+ recursivelyFillYields(yields, item.output);
47
+ item = item.sibling;
48
+ } while (item);
49
+ } else {
50
+ // $FlowFixMe: Refinement issue. If it is not a Fiber or null, it is a yield
51
+ yields.push(output);
52
+ }
53
+}
54
+
55
+function handleCoroutine(unitOfWork : Fiber) {
56
+ var coroutine = (unitOfWork.input : ?ReactCoroutine);
57
+ if (!coroutine) {
58
+ throw new Error('Should be resolved by now');
59
+ }
60
+
61
+ if (unitOfWork.stage === 0) {
62
+ // First step of the coroutine has completed. Now we need to do the second.
63
+ // TODO: It would be nice to have a multi stage coroutine represented by a
64
+ // single component, or at least tail call optimize nested ones.
65
+ // TODO: If we end up not using multi stage coroutines, we could also reuse
66
+ // the tag field to switch between the two stages.
67
+ unitOfWork.stage = 1;
68
+
69
+ // Build up the yields.
70
+ // TODO: Compare this to a generator or opaque helpers like Children.
71
+ var yields : Array<ReifiedYield> = [];
72
+ var child = unitOfWork.child;
73
+ while (child) {
74
+ recursivelyFillYields(yields, child.output);
75
+ child = child.sibling;
76
+ }
77
+ var fn = coroutine.handler;
78
+ var props = coroutine.props;
79
+ var nextChildren = fn(props, yields);
80
+
81
+ unitOfWork.stateNode = ReactChildFiber.reconcileChildFibers(
82
+ unitOfWork,
83
+ unitOfWork.stateNode,
84
+ nextChildren
85
+ );
86
+ return unitOfWork.stateNode;
87
+ } else {
88
+ // The coroutine is now complete.
89
+ transferOutput(unitOfWork.stateNode, unitOfWork);
90
+ return null;
91
+ }
92
+}
93
+
94
exports.completeWork = function(unitOfWork : Fiber) : ?Fiber {
95
switch (unitOfWork.tag) {
96
case FunctionalComponent:
29
- // $FlowFixMe
97
console.log('/functional component', unitOfWork.input.type.name);
98
+ transferOutput(unitOfWork.child, unitOfWork);
99
break;
100
case ClassComponent:
33
- // $FlowFixMe
101
console.log('/class component', unitOfWork.input.type.name);
102
+ transferOutput(unitOfWork.child, unitOfWork);
103
break;
104
case HostComponent:
37
- // $FlowFixMe
105
console.log('/host component', unitOfWork.input.type);
106
break;
107
+ case CoroutineComponent:
108
+ console.log('/coroutine component', unitOfWork.input.handler.name);
109
+ return handleCoroutine(unitOfWork);
110
+ case YieldComponent:
111
+ // Does nothing.
112
+ break;
113
+
114
+ // Error cases
115
case IndeterminateComponent:
116
throw new Error('An indeterminate component should have become determinate before completing.');
117
default:
src/renderers/shared/fiber/ReactReifiedYield.js
new
+37
@@ -0,0 +1,37 @@
1
+/**
2
+ * Copyright 2013-present, Facebook, Inc.
3
+ * All rights reserved.
4
+ *
5
+ * This source code is licensed under the BSD-style license found in the
6
+ * LICENSE file in the root directory of this source tree. An additional grant
7
+ * of patent rights can be found in the PATENTS file in the same directory.
8
+ *
9
+ * @providesModule ReactReifiedYield
10
+ * @flow
11
+ */
12
+
13
+'use strict';
14
+
15
+import type { ReactYield } from 'ReactCoroutine';
16
+import type { Fiber } from 'ReactFiber';
17
+
18
+var ReactFiber = require('ReactFiber');
19
+
20
+export type ReifiedYield = { continuation: Fiber, props: Object };
21
+
22
+exports.createReifiedYield = function(yieldNode : ReactYield) : ReifiedYield {
23
+ var fiber = ReactFiber.createFiberFromElementType(yieldNode.continuation);
24
+ // Hacky way to store the continuation
25
+ fiber.input = yieldNode.continuation;
26
+ return {
27
+ continuation: fiber,
28
+ props: yieldNode.props,
29
+ };
30
+};
31
+
32
+exports.createUpdatedReifiedYield = function(previousYield : ReifiedYield, yieldNode : ReactYield) : ReifiedYield {
33
+ return {
34
+ continuation: previousYield.continuation,
35
+ props: yieldNode.props,
36
+ };
37
+};
src/renderers/shared/fiber/ReactStateNode.js
deleted
-23
@@ -1,23 +0,0 @@
1
-/**
2
- * Copyright 2013-present, Facebook, Inc.
3
- * All rights reserved.
4
- *
5
- * This source code is licensed under the BSD-style license found in the
6
- * LICENSE file in the root directory of this source tree. An additional grant
7
- * of patent rights can be found in the PATENTS file in the same directory.
8
- *
9
- * @providesModule ReactStateNode
10
- * @flow
11
- */
12
-
13
-'use strict';
14
-
15
-type StateNode = {
16
- next: ?{ [key: string]: StateNode },
17
-};
18
-
19
-module.exports = function() : StateNode {
20
- return {
21
- next: null,
22
- };
23
-};
src/renderers/shared/fiber/isomorphic/ReactCoroutine.js
new
+109
@@ -0,0 +1,109 @@
1
+/**
2
+ * Copyright 2014-present, Facebook, Inc.
3
+ * All rights reserved.
4
+ *
5
+ * This source code is licensed under the BSD-style license found in the
6
+ * LICENSE file in the root directory of this source tree. An additional grant
7
+ * of patent rights can be found in the PATENTS file in the same directory.
8
+ *
9
+ * @providesModule ReactCoroutine
10
+ * @flow
11
+ */
12
+
13
+'use strict';
14
+
15
+import type { ReactNodeList } from 'ReactTypes';
16
+
17
+// The Symbol used to tag the special React types. If there is no native Symbol
18
+// 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;
22
+
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
+
30
+export type ReactCoroutine = {
31
+ $$typeof: Symbol | number,
32
+ key: ?string,
33
+ children: any,
34
+ // This should be a more specific CoroutineHandler
35
+ handler: (props: any, yields: Array<ReifiedYield>) => ReactNodeList,
36
+ props: mixed,
37
+};
38
+export type ReactYield = {
39
+ $$typeof: Symbol | number,
40
+ key: ?string,
41
+ props: Object,
42
+ continuation: mixed
43
+};
44
+
45
+exports.createCoroutine = function<T>(children : mixed, handler : CoroutineHandler<T>, props : T, key : ?string = null) : ReactCoroutine {
46
+ var coroutine = {
47
+ // This tag allow us to uniquely identify this as a React Coroutine
48
+ $$typeof: REACT_COROUTINE_TYPE,
49
+ key: key == null ? null : '' + key,
50
+ children: children,
51
+ handler: handler,
52
+ props: props,
53
+ };
54
+
55
+ if (__DEV__) {
56
+ // TODO: Add _store property for marking this as validated.
57
+ if (Object.freeze) {
58
+ Object.freeze(coroutine.props);
59
+ Object.freeze(coroutine);
60
+ }
61
+ }
62
+
63
+ return coroutine;
64
+};
65
+
66
+exports.createYield = function(props : mixed, continuation : mixed, key : ?string = null) {
67
+ var yieldNode = {
68
+ // This tag allow us to uniquely identify this as a React Yield
69
+ $$typeof: REACT_YIELD_TYPE,
70
+ key: key == null ? null : '' + key,
71
+ props: props,
72
+ continuation: continuation,
73
+ };
74
+
75
+ if (__DEV__) {
76
+ // TODO: Add _store property for marking this as validated.
77
+ if (Object.freeze) {
78
+ Object.freeze(yieldNode.props);
79
+ Object.freeze(yieldNode);
80
+ }
81
+ }
82
+
83
+ return yieldNode;
84
+};
85
+
86
+/**
87
+ * Verifies the object is a coroutine object.
88
+ */
89
+exports.isCoroutine = function(object : mixed) : boolean {
90
+ return (
91
+ typeof object === 'object' &&
92
+ object !== null &&
93
+ object.$$typeof === REACT_COROUTINE_TYPE
94
+ );
95
+};
96
+
97
+/**
98
+ * Verifies the object is a yield object.
99
+ */
100
+exports.isYield = function(object : mixed) : boolean {
101
+ return (
102
+ typeof object === 'object' &&
103
+ object !== null &&
104
+ object.$$typeof === REACT_YIELD_TYPE
105
+ );
106
+};
107
+
108
+exports.REACT_YIELD_TYPE = REACT_YIELD_TYPE;
109
+exports.REACT_COROUTINE_TYPE = REACT_COROUTINE_TYPE;
src/renderers/shared/fiber/isomorphic/ReactTypes.js
new
+25
@@ -0,0 +1,25 @@
1
+/**
2
+ * Copyright 2014-present, Facebook, Inc.
3
+ * All rights reserved.
4
+ *
5
+ * This source code is licensed under the BSD-style license found in the
6
+ * LICENSE file in the root directory of this source tree. An additional grant
7
+ * of patent rights can be found in the PATENTS file in the same directory.
8
+ *
9
+ * @providesModule ReactTypes
10
+ * @flow
11
+ */
12
+
13
+'use strict';
14
+
15
+import type { ReactCoroutine, ReactYield } from 'ReactCoroutine';
16
+
17
+export type ReactNode = ReactElement | ReactCoroutine | ReactYield | ReactText | ReactFragment;
18
+
19
+export type ReactFragment = ReactEmpty | Iterable<ReactNode>;
20
+
21
+export type ReactNodeList = ReactEmpty | ReactNode;
22
+
23
+export type ReactText = string | number;
24
+
25
+export type ReactEmpty = null | void | boolean;
src/renderers/shared/stack/reconciler/__tests__/ReactComponent-test.js
+1
-7
@@ -280,14 +280,8 @@ describe('ReactComponent', function() {
280
'or a class/function (for composite components) but got: null.'
281
);
282
283
- var Z = {};
284
- expect(() => ReactTestUtils.renderIntoDocument(<Z />)).toThrowError(
285
- 'Element type is invalid: expected a string (for built-in components) ' +
286
- 'or a class/function (for composite components) but got: object.'
287
- );
288
-
283
// One warning for each element creation
290
- expect(console.error.calls.count()).toBe(3);
284
+ expect(console.error.calls.count()).toBe(2);
285
});
286
287
});