From fd4f74ef955b6d627af8a9fc55a61c828564a67e Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Thu, 26 May 2016 18:13:21 -0700 Subject: [PATCH] Coroutines --- .../classic/element/ReactElement.js | 2 + .../classic/element/ReactElementValidator.js | 3 +- .../noop/__tests__/ReactNoop-test.js | 55 ++++++++- src/renderers/shared/fiber/ReactChildFiber.js | 97 +++++++++++----- src/renderers/shared/fiber/ReactFiber.js | 70 ++++++++--- .../shared/fiber/ReactFiberBeginWork.js | 51 ++++++-- .../shared/fiber/ReactFiberCompleteWork.js | 81 ++++++++++++- .../shared/fiber/ReactReifiedYield.js | 37 ++++++ src/renderers/shared/fiber/ReactStateNode.js | 23 ---- .../shared/fiber/isomorphic/ReactCoroutine.js | 109 ++++++++++++++++++ .../shared/fiber/isomorphic/ReactTypes.js | 25 ++++ .../__tests__/ReactComponent-test.js | 8 +- 12 files changed, 471 insertions(+), 90 deletions(-) create mode 100644 src/renderers/shared/fiber/ReactReifiedYield.js delete mode 100644 src/renderers/shared/fiber/ReactStateNode.js create mode 100644 src/renderers/shared/fiber/isomorphic/ReactCoroutine.js create mode 100644 src/renderers/shared/fiber/isomorphic/ReactTypes.js diff --git a/src/isomorphic/classic/element/ReactElement.js b/src/isomorphic/classic/element/ReactElement.js index 4e815449247af..98af361b097ba 100644 --- a/src/isomorphic/classic/element/ReactElement.js +++ b/src/isomorphic/classic/element/ReactElement.js @@ -398,4 +398,6 @@ ReactElement.isValidElement = function(object) { ); }; +ReactElement.REACT_ELEMENT_TYPE = REACT_ELEMENT_TYPE; + module.exports = ReactElement; diff --git a/src/isomorphic/classic/element/ReactElementValidator.js b/src/isomorphic/classic/element/ReactElementValidator.js index 2b7e9da3d56ad..4d4121f9810c3 100644 --- a/src/isomorphic/classic/element/ReactElementValidator.js +++ b/src/isomorphic/classic/element/ReactElementValidator.js @@ -184,7 +184,8 @@ function validatePropTypes(element) { var ReactElementValidator = { createElement: function(type, props, children) { - var validType = typeof type === 'string' || typeof type === 'function'; + var validType = typeof type === 'string' || typeof type === 'function' || + (type !== null && typeof type === 'object'); // We warn in this case but don't throw. We expect the element creation to // succeed and there will likely be errors in render. warning( diff --git a/src/renderers/noop/__tests__/ReactNoop-test.js b/src/renderers/noop/__tests__/ReactNoop-test.js index 324f41cca9bfc..1ce47ee5c2dca 100644 --- a/src/renderers/noop/__tests__/ReactNoop-test.js +++ b/src/renderers/noop/__tests__/ReactNoop-test.js @@ -13,14 +13,17 @@ var React; var ReactNoop; +var ReactCoroutine; describe('ReactComponent', function() { beforeEach(function() { React = require('React'); ReactNoop = require('ReactNoop'); + ReactCoroutine = require('ReactCoroutine'); + spyOn(console, 'log'); }); - /*it('should render a simple component', function() { + it('should render a simple component', function() { function Bar() { return
Hello World
; @@ -33,7 +36,7 @@ describe('ReactComponent', function() { ReactNoop.render(); ReactNoop.flush(); - });*/ + }); it('should render a simple component, in steps if needed', function() { @@ -49,12 +52,54 @@ describe('ReactComponent', function() { } ReactNoop.render(); - console.log('Nothing done'); + // console.log('Nothing done'); ReactNoop.flushLowPri(7); - console.log('Yield'); + // console.log('Yield'); ReactNoop.flushLowPri(50); - console.log('Done'); + // console.log('Done'); }); + it('should render a coroutine', function() { + + function Continuation({ isSame }) { + return {isSame ? 'foo==bar' : 'foo!=bar'}; + } + + // An alternative API could mark Continuation as something that needs + // yielding. E.g. Continuation.yieldType = 123; + function Child({ bar }) { + return ReactCoroutine.createYield({ + bar: bar, + }, Continuation, null); + } + + function Indirection() { + return [, ]; + } + + function HandleYields(props, yields) { + return yields.map(y => + + ); + } + + // An alternative API could mark Parent as something that needs + // yielding. E.g. Parent.handler = HandleYields; + function Parent(props) { + return ReactCoroutine.createCoroutine( + props.children, + HandleYields, + props + ); + } + + function App() { + return
; + } + + ReactNoop.render(); + ReactNoop.flush(); + + }); }); diff --git a/src/renderers/shared/fiber/ReactChildFiber.js b/src/renderers/shared/fiber/ReactChildFiber.js index 9bc02fb576007..bbb666cab0903 100644 --- a/src/renderers/shared/fiber/ReactChildFiber.js +++ b/src/renderers/shared/fiber/ReactChildFiber.js @@ -12,33 +12,53 @@ 'use strict'; +import type { ReactCoroutine, ReactYield } from 'ReactCoroutine'; import type { Fiber } from 'ReactFiber'; -var ReactElement = require('ReactElement'); +import type { ReactNodeList } from 'ReactTypes'; -var ReactFiber = require('ReactFiber'); - -type ReactNode = ReactElement | ReactFragment | ReactText; - -type ReactFragment = Iterable; - -type ReactNodeList = ReactNode | ReactEmpty; +var { + REACT_ELEMENT_TYPE, +} = require('ReactElement'); +var { + REACT_COROUTINE_TYPE, + REACT_YIELD_TYPE, +} = require('ReactCoroutine'); -type ReactText = string | number; - -type ReactEmpty = null | void | boolean; +var ReactFiber = require('ReactFiber'); +var ReactReifiedYield = require('ReactReifiedYield'); function createSubsequentChild(parent : Fiber, previousSibling : Fiber, newChildren) : Fiber { if (typeof newChildren !== 'object' || newChildren === null) { return previousSibling; } - if (ReactElement.isValidElement(newChildren)) { - var element = (newChildren : ReactElement); - var child = ReactFiber.createFiberFromElement(element); - previousSibling.sibling = child; - child.parent = parent; - return child; + switch (newChildren.$$typeof) { + case REACT_ELEMENT_TYPE: { + const element = (newChildren : ReactElement); + const child = ReactFiber.createFiberFromElement(element); + previousSibling.sibling = child; + child.parent = parent; + return child; + } + + case REACT_COROUTINE_TYPE: { + const coroutine = (newChildren : ReactCoroutine); + const child = ReactFiber.createFiberFromCoroutine(coroutine); + previousSibling.sibling = child; + child.parent = parent; + return child; + } + + case REACT_YIELD_TYPE: { + const yieldNode = (newChildren : ReactYield); + const reifiedYield = ReactReifiedYield.createReifiedYield(yieldNode); + const child = ReactFiber.createFiberFromYield(yieldNode); + child.output = reifiedYield; + previousSibling.sibling = child; + child.parent = parent; + return child; + } } if (Array.isArray(newChildren)) { @@ -48,39 +68,62 @@ function createSubsequentChild(parent : Fiber, previousSibling : Fiber, newChild } return prev; } else { + console.log('Unknown child', newChildren); return previousSibling; } } function createFirstChild(parent, newChildren) { if (typeof newChildren !== 'object' || newChildren === null) { - parent.child = null; return null; } - if (ReactElement.isValidElement(newChildren)) { - var element = (newChildren : ReactElement); - var child = ReactFiber.createFiberFromElement(element); - parent.child = child; - child.parent = parent; - return child; + switch (newChildren.$$typeof) { + case REACT_ELEMENT_TYPE: { + const element = (newChildren : ReactElement); + const child = ReactFiber.createFiberFromElement(element); + child.parent = parent; + return child; + } + + case REACT_COROUTINE_TYPE: { + const coroutine = (newChildren : ReactCoroutine); + const child = ReactFiber.createFiberFromCoroutine(coroutine); + child.parent = parent; + return child; + } + + case REACT_YIELD_TYPE: { + // A yield results in a fragment fiber whose output is the continuation. + // TODO: When there is only a single child, we can optimize this to avoid + // the fragment. + const yieldNode = (newChildren : ReactYield); + const reifiedYield = ReactReifiedYield.createReifiedYield(yieldNode); + const child = ReactFiber.createFiberFromYield(yieldNode); + child.output = reifiedYield; + child.parent = parent; + return child; + } } if (Array.isArray(newChildren)) { + var first : ?Fiber = null; var prev : ?Fiber = null; for (var i = 0; i < newChildren.length; i++) { if (prev == null) { prev = createFirstChild(parent, newChildren[i]); + first = prev; } else { prev = createSubsequentChild(parent, prev, newChildren[i]); } } + return first; } else { - parent.child = null; + console.log('Unknown child', newChildren); return null; } } -exports.reconcileChildFibers = function(parent : Fiber, newChildren : ReactNodeList) : void { - createFirstChild(parent, newChildren); +exports.reconcileChildFibers = function(parent : Fiber, firstChild : ?Fiber, newChildren : ReactNodeList) : ?Fiber { + return createFirstChild(parent, newChildren); }; diff --git a/src/renderers/shared/fiber/ReactFiber.js b/src/renderers/shared/fiber/ReactFiber.js index f4a86bbf19009..eb17068b6c024 100644 --- a/src/renderers/shared/fiber/ReactFiber.js +++ b/src/renderers/shared/fiber/ReactFiber.js @@ -17,30 +17,42 @@ var { IndeterminateComponent, ClassComponent, HostComponent, + CoroutineComponent, + YieldComponent, } = ReactTypesOfWork; -type StateNode = {}; -type EffectHandler = () => void; -type EffectTag = number; +var ReactElement = require('ReactElement'); + +import type { ReactCoroutine, ReactYield } from 'ReactCoroutine'; export type Fiber = { + // Tag identifying the type of fiber. tag: number, - parent: ?Fiber, + // Singly Linked List Tree Structure. + parent: ?Fiber, // Consider a regenerated temporary parent stack instead. child: ?Fiber, sibling: ?Fiber, - input: ?Object, - output: ?Object, + // Input is the data coming into process this fiber. Arguments. + input: any, // This type will be more specific once we overload the tag. + // Output is the return value of this fiber, or a linked list of return values + // if this returns multiple values. Such as a fragment. + output: any, // This type will be more specific once we overload the tag. + + // Used by multi-stage coroutines. + stage: number, // Consider reusing the tag field instead. + // This will be used to quickly determine if a subtree has no pending changes. hasPendingChanges: bool, - stateNode: StateNode, + // The local state associated with this fiber. + stateNode: ?Object, }; -var createFiber = function(tag : number, handlerTag : number) : Fiber { +var createFiber = function(tag : number) : Fiber { return { tag: tag, @@ -52,9 +64,11 @@ var createFiber = function(tag : number, handlerTag : number) : Fiber { input: null, output: null, + stage: 0, + hasPendingChanges: true, - stateNode: {}, + stateNode: null, }; }; @@ -64,17 +78,39 @@ function shouldConstruct(Component) { } exports.createFiberFromElement = function(element : ReactElement) { + const fiber = exports.createFiberFromElementType(element.type); + if (typeof element.type === 'object') { + // Hacky McHack + element = ReactElement(fiber.input, null, element.ref, null, null, null, element.props); + } + fiber.input = element; + return fiber; +}; + +exports.createFiberFromElementType = function(type : mixed) { let fiber; - if (typeof element.type === 'function') { - fiber = shouldConstruct(element.type) ? - createFiber(ClassComponent, 0) : - createFiber(IndeterminateComponent, 0); - } else if (typeof element.type === 'string') { - fiber = createFiber(HostComponent, 1); + if (typeof type === 'function') { + fiber = shouldConstruct(type) ? + createFiber(ClassComponent) : + createFiber(IndeterminateComponent); + } else if (typeof type === 'string') { + fiber = createFiber(HostComponent); + } else if (typeof type === 'object' && type !== null) { + // Currently assumed to be a continuation and therefore is a fiber already. + fiber = type; } else { - throw new Error('Unknown component type: ' + typeof element.type); + throw new Error('Unknown component type: ' + typeof type); } + return fiber; +}; - fiber.input = element; +exports.createFiberFromCoroutine = function(coroutine : ReactCoroutine) { + const fiber = createFiber(CoroutineComponent); + fiber.input = coroutine; + return fiber; +}; + +exports.createFiberFromYield = function(yieldNode : ReactYield) { + const fiber = createFiber(YieldComponent); return fiber; }; diff --git a/src/renderers/shared/fiber/ReactFiberBeginWork.js b/src/renderers/shared/fiber/ReactFiberBeginWork.js index 7ca3d43e3c003..19026242f9ec3 100644 --- a/src/renderers/shared/fiber/ReactFiberBeginWork.js +++ b/src/renderers/shared/fiber/ReactFiberBeginWork.js @@ -12,6 +12,7 @@ 'use strict'; +import type { ReactCoroutine } from 'ReactCoroutine'; import type { Fiber } from 'ReactFiber'; var ReactChildFiber = require('ReactChildFiber'); @@ -21,6 +22,8 @@ var { FunctionalComponent, ClassComponent, HostComponent, + CoroutineComponent, + YieldComponent, } = ReactTypesOfWork; function getElement(unitOfWork) : ReactElement { @@ -38,19 +41,21 @@ function updateFunctionalComponent(unitOfWork) { console.log('perform work on:', fn.name); var nextChildren = fn(props); - ReactChildFiber.reconcileChildFibers( + unitOfWork.child = ReactChildFiber.reconcileChildFibers( unitOfWork, + unitOfWork.child, nextChildren ); } function updateHostComponent(unitOfWork) { var element = getElement(unitOfWork); - console.log('host component', element.type); + console.log('host component', element.type, typeof element.props.children === 'string' ? element.props.children : ''); var nextChildren = element.props.children; - ReactChildFiber.reconcileChildFibers( + unitOfWork.child = ReactChildFiber.reconcileChildFibers( unitOfWork, + unitOfWork.child, nextChildren ); } @@ -69,13 +74,27 @@ function mountIndeterminateComponent(unitOfWork) { // Proceed under the assumption that this is a functional component unitOfWork.tag = FunctionalComponent; } - ReactChildFiber.reconcileChildFibers( + unitOfWork.child = ReactChildFiber.reconcileChildFibers( unitOfWork, + unitOfWork.child, value ); } -exports.beginWork = function(unitOfWork : Fiber) : ?Fiber { +function updateCoroutineComponent(unitOfWork) { + var coroutine = (unitOfWork.input : ?ReactCoroutine); + if (!coroutine) { + throw new Error('Should be resolved by now'); + } + console.log('begin coroutine', coroutine.handler.name); + unitOfWork.child = ReactChildFiber.reconcileChildFibers( + unitOfWork, + unitOfWork.child, + coroutine.children + ); +} + +function beginWork(unitOfWork : Fiber) : ?Fiber { switch (unitOfWork.tag) { case IndeterminateComponent: mountIndeterminateComponent(unitOfWork); @@ -84,14 +103,32 @@ exports.beginWork = function(unitOfWork : Fiber) : ?Fiber { updateFunctionalComponent(unitOfWork); break; case ClassComponent: - // $FlowFixMe console.log('class component', unitOfWork.input.type.name); break; case HostComponent: updateHostComponent(unitOfWork); break; + case CoroutineComponent: + // Reset the stage to zero. + unitOfWork.stage = 0; + updateCoroutineComponent(unitOfWork); + // This doesn't take arbitrary time so we could synchronously just begin + // eagerly do the work of unitOfWork.child as an optimization. + if (unitOfWork.child) { + return beginWork(unitOfWork.child); + } + break; + case YieldComponent: + // A yield component is just a placeholder, we can just run through the + // next one immediately. + if (unitOfWork.sibling) { + return beginWork(unitOfWork.sibling); + } + return null; default: throw new Error('Unknown unit of work tag'); } return unitOfWork.child; -}; +} + +exports.beginWork = beginWork; diff --git a/src/renderers/shared/fiber/ReactFiberCompleteWork.js b/src/renderers/shared/fiber/ReactFiberCompleteWork.js index 5b7277788f485..827075f004312 100644 --- a/src/renderers/shared/fiber/ReactFiberCompleteWork.js +++ b/src/renderers/shared/fiber/ReactFiberCompleteWork.js @@ -12,8 +12,11 @@ 'use strict'; +import type { ReactCoroutine } from 'ReactCoroutine'; import type { Fiber } from 'ReactFiber'; +import type { ReifiedYield } from 'ReactReifiedYield'; + var ReactChildFiber = require('ReactChildFiber'); var ReactTypesOfWork = require('ReactTypesOfWork'); var { @@ -21,22 +24,94 @@ var { FunctionalComponent, ClassComponent, HostComponent, + CoroutineComponent, + YieldComponent, } = ReactTypesOfWork; +function transferOutput(child : ?Fiber, parent : Fiber) { + // If we have a single result, we just pass that through as the output to + // avoid unnecessary traversal. When we have multiple output, we just pass + // the linked list of fibers that has the individual output values. + parent.output = (child && !child.sibling) ? child.output : child; +} + +function recursivelyFillYields(yields, output : ?Fiber | ?ReifiedYield) { + if (!output) { + // Ignore nulls etc. + } else if (output.tag !== undefined) { // TODO: Fix this fragile duck test. + // Detect if this is a fiber, if so it is a fragment result. + // $FlowFixMe: Refinement issue. + var item = (output : Fiber); + do { + recursivelyFillYields(yields, item.output); + item = item.sibling; + } while (item); + } else { + // $FlowFixMe: Refinement issue. If it is not a Fiber or null, it is a yield + yields.push(output); + } +} + +function handleCoroutine(unitOfWork : Fiber) { + var coroutine = (unitOfWork.input : ?ReactCoroutine); + if (!coroutine) { + throw new Error('Should be resolved by now'); + } + + if (unitOfWork.stage === 0) { + // First step of the coroutine has completed. Now we need to do the second. + // TODO: It would be nice to have a multi stage coroutine represented by a + // single component, or at least tail call optimize nested ones. + // TODO: If we end up not using multi stage coroutines, we could also reuse + // the tag field to switch between the two stages. + unitOfWork.stage = 1; + + // Build up the yields. + // TODO: Compare this to a generator or opaque helpers like Children. + var yields : Array = []; + var child = unitOfWork.child; + while (child) { + recursivelyFillYields(yields, child.output); + child = child.sibling; + } + var fn = coroutine.handler; + var props = coroutine.props; + var nextChildren = fn(props, yields); + + unitOfWork.stateNode = ReactChildFiber.reconcileChildFibers( + unitOfWork, + unitOfWork.stateNode, + nextChildren + ); + return unitOfWork.stateNode; + } else { + // The coroutine is now complete. + transferOutput(unitOfWork.stateNode, unitOfWork); + return null; + } +} + exports.completeWork = function(unitOfWork : Fiber) : ?Fiber { switch (unitOfWork.tag) { case FunctionalComponent: - // $FlowFixMe console.log('/functional component', unitOfWork.input.type.name); + transferOutput(unitOfWork.child, unitOfWork); break; case ClassComponent: - // $FlowFixMe console.log('/class component', unitOfWork.input.type.name); + transferOutput(unitOfWork.child, unitOfWork); break; case HostComponent: - // $FlowFixMe console.log('/host component', unitOfWork.input.type); break; + case CoroutineComponent: + console.log('/coroutine component', unitOfWork.input.handler.name); + return handleCoroutine(unitOfWork); + case YieldComponent: + // Does nothing. + break; + + // Error cases case IndeterminateComponent: throw new Error('An indeterminate component should have become determinate before completing.'); default: diff --git a/src/renderers/shared/fiber/ReactReifiedYield.js b/src/renderers/shared/fiber/ReactReifiedYield.js new file mode 100644 index 0000000000000..a47887bc56ba1 --- /dev/null +++ b/src/renderers/shared/fiber/ReactReifiedYield.js @@ -0,0 +1,37 @@ +/** + * Copyright 2013-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule ReactReifiedYield + * @flow + */ + +'use strict'; + +import type { ReactYield } from 'ReactCoroutine'; +import type { Fiber } from 'ReactFiber'; + +var ReactFiber = require('ReactFiber'); + +export type ReifiedYield = { continuation: Fiber, props: Object }; + +exports.createReifiedYield = function(yieldNode : ReactYield) : ReifiedYield { + var fiber = ReactFiber.createFiberFromElementType(yieldNode.continuation); + // Hacky way to store the continuation + fiber.input = yieldNode.continuation; + return { + continuation: fiber, + props: yieldNode.props, + }; +}; + +exports.createUpdatedReifiedYield = function(previousYield : ReifiedYield, yieldNode : ReactYield) : ReifiedYield { + return { + continuation: previousYield.continuation, + props: yieldNode.props, + }; +}; diff --git a/src/renderers/shared/fiber/ReactStateNode.js b/src/renderers/shared/fiber/ReactStateNode.js deleted file mode 100644 index c7ea45b3b4064..0000000000000 --- a/src/renderers/shared/fiber/ReactStateNode.js +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright 2013-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @providesModule ReactStateNode - * @flow - */ - -'use strict'; - -type StateNode = { - next: ?{ [key: string]: StateNode }, -}; - -module.exports = function() : StateNode { - return { - next: null, - }; -}; diff --git a/src/renderers/shared/fiber/isomorphic/ReactCoroutine.js b/src/renderers/shared/fiber/isomorphic/ReactCoroutine.js new file mode 100644 index 0000000000000..a03934ea56fcd --- /dev/null +++ b/src/renderers/shared/fiber/isomorphic/ReactCoroutine.js @@ -0,0 +1,109 @@ +/** + * Copyright 2014-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule ReactCoroutine + * @flow + */ + +'use strict'; + +import type { ReactNodeList } from 'ReactTypes'; + +// The Symbol used to tag the special React types. If there is no native Symbol +// nor polyfill, then a plain number is used for performance. +var REACT_COROUTINE_TYPE = + (typeof Symbol === 'function' && Symbol.for && Symbol.for('react.coroutine')) || + 0xeac8; + +var REACT_YIELD_TYPE = + (typeof Symbol === 'function' && Symbol.for && Symbol.for('react.yield')) || + 0xeac9; + +type ReifiedYield = { continuation: Object, props: Object }; +type CoroutineHandler = (props: T, yields: Array) => ReactNodeList; + +export type ReactCoroutine = { + $$typeof: Symbol | number, + key: ?string, + children: any, + // This should be a more specific CoroutineHandler + handler: (props: any, yields: Array) => ReactNodeList, + props: mixed, +}; +export type ReactYield = { + $$typeof: Symbol | number, + key: ?string, + props: Object, + continuation: mixed +}; + +exports.createCoroutine = function(children : mixed, handler : CoroutineHandler, props : T, key : ?string = null) : ReactCoroutine { + var coroutine = { + // This tag allow us to uniquely identify this as a React Coroutine + $$typeof: REACT_COROUTINE_TYPE, + key: key == null ? null : '' + key, + children: children, + handler: handler, + props: props, + }; + + if (__DEV__) { + // TODO: Add _store property for marking this as validated. + if (Object.freeze) { + Object.freeze(coroutine.props); + Object.freeze(coroutine); + } + } + + return coroutine; +}; + +exports.createYield = function(props : mixed, continuation : mixed, key : ?string = null) { + var yieldNode = { + // This tag allow us to uniquely identify this as a React Yield + $$typeof: REACT_YIELD_TYPE, + key: key == null ? null : '' + key, + props: props, + continuation: continuation, + }; + + if (__DEV__) { + // TODO: Add _store property for marking this as validated. + if (Object.freeze) { + Object.freeze(yieldNode.props); + Object.freeze(yieldNode); + } + } + + return yieldNode; +}; + +/** + * Verifies the object is a coroutine object. + */ +exports.isCoroutine = function(object : mixed) : boolean { + return ( + typeof object === 'object' && + object !== null && + object.$$typeof === REACT_COROUTINE_TYPE + ); +}; + +/** + * Verifies the object is a yield object. + */ +exports.isYield = function(object : mixed) : boolean { + return ( + typeof object === 'object' && + object !== null && + object.$$typeof === REACT_YIELD_TYPE + ); +}; + +exports.REACT_YIELD_TYPE = REACT_YIELD_TYPE; +exports.REACT_COROUTINE_TYPE = REACT_COROUTINE_TYPE; diff --git a/src/renderers/shared/fiber/isomorphic/ReactTypes.js b/src/renderers/shared/fiber/isomorphic/ReactTypes.js new file mode 100644 index 0000000000000..4c8c69571c222 --- /dev/null +++ b/src/renderers/shared/fiber/isomorphic/ReactTypes.js @@ -0,0 +1,25 @@ +/** + * Copyright 2014-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule ReactTypes + * @flow + */ + +'use strict'; + +import type { ReactCoroutine, ReactYield } from 'ReactCoroutine'; + +export type ReactNode = ReactElement | ReactCoroutine | ReactYield | ReactText | ReactFragment; + +export type ReactFragment = ReactEmpty | Iterable; + +export type ReactNodeList = ReactEmpty | ReactNode; + +export type ReactText = string | number; + +export type ReactEmpty = null | void | boolean; diff --git a/src/renderers/shared/stack/reconciler/__tests__/ReactComponent-test.js b/src/renderers/shared/stack/reconciler/__tests__/ReactComponent-test.js index f97ae25c09776..cd0c13d6e5f92 100644 --- a/src/renderers/shared/stack/reconciler/__tests__/ReactComponent-test.js +++ b/src/renderers/shared/stack/reconciler/__tests__/ReactComponent-test.js @@ -280,14 +280,8 @@ describe('ReactComponent', function() { 'or a class/function (for composite components) but got: null.' ); - var Z = {}; - expect(() => ReactTestUtils.renderIntoDocument()).toThrowError( - 'Element type is invalid: expected a string (for built-in components) ' + - 'or a class/function (for composite components) but got: object.' - ); - // One warning for each element creation - expect(console.error.calls.count()).toBe(3); + expect(console.error.calls.count()).toBe(2); }); });