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 b71978e2cd75b..1ce47ee5c2dca 100644 --- a/src/renderers/noop/__tests__/ReactNoop-test.js +++ b/src/renderers/noop/__tests__/ReactNoop-test.js @@ -13,11 +13,14 @@ 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() { @@ -38,11 +41,14 @@ describe('ReactComponent', function() { it('should render a simple component, in steps if needed', function() { function Bar() { - return
Hello World
; + return
Hello World
; } function Foo() { - return ; + return [ + , + , + ]; } ReactNoop.render(); @@ -53,5 +59,47 @@ describe('ReactComponent', function() { // 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 new file mode 100644 index 0000000000000..bbb666cab0903 --- /dev/null +++ b/src/renderers/shared/fiber/ReactChildFiber.js @@ -0,0 +1,129 @@ +/** + * 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 ReactChildFiber + * @flow + */ + +'use strict'; + +import type { ReactCoroutine, ReactYield } from 'ReactCoroutine'; +import type { Fiber } from 'ReactFiber'; + +import type { ReactNodeList } from 'ReactTypes'; + +var { + REACT_ELEMENT_TYPE, +} = require('ReactElement'); +var { + REACT_COROUTINE_TYPE, + REACT_YIELD_TYPE, +} = require('ReactCoroutine'); + +var ReactFiber = require('ReactFiber'); +var ReactReifiedYield = require('ReactReifiedYield'); + +function createSubsequentChild(parent : Fiber, previousSibling : Fiber, newChildren) : Fiber { + if (typeof newChildren !== 'object' || newChildren === null) { + return previousSibling; + } + + 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)) { + let prev : Fiber = previousSibling; + for (var i = 0; i < newChildren.length; i++) { + prev = createSubsequentChild(parent, prev, newChildren[i]); + } + return prev; + } else { + console.log('Unknown child', newChildren); + return previousSibling; + } +} + +function createFirstChild(parent, newChildren) { + if (typeof newChildren !== 'object' || newChildren === null) { + return null; + } + + 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 { + console.log('Unknown child', newChildren); + return null; + } +} + +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 9e99e8998c6ba..eb17068b6c024 100644 --- a/src/renderers/shared/fiber/ReactFiber.js +++ b/src/renderers/shared/fiber/ReactFiber.js @@ -12,31 +12,47 @@ 'use strict'; -type StateNode = {}; -type EffectHandler = () => void; -type EffectTag = number; +var ReactTypesOfWork = require('ReactTypesOfWork'); +var { + IndeterminateComponent, + ClassComponent, + HostComponent, + CoroutineComponent, + YieldComponent, +} = ReactTypesOfWork; + +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. - handler: EffectHandler, - handlerTag: EffectTag, + // 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, }; -module.exports = function(tag : number) : Fiber { +var createFiber = function(tag : number) : Fiber { return { tag: tag, @@ -48,12 +64,53 @@ module.exports = function(tag : number) : Fiber { input: null, output: null, - handler: function() {}, - handlerTag: 0, + stage: 0, hasPendingChanges: true, - stateNode: {}, + stateNode: null, }; }; + +function shouldConstruct(Component) { + return !!(Component.prototype && Component.prototype.isReactComponent); +} + +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 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 type); + } + return fiber; +}; + +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 new file mode 100644 index 0000000000000..19026242f9ec3 --- /dev/null +++ b/src/renderers/shared/fiber/ReactFiberBeginWork.js @@ -0,0 +1,134 @@ +/** + * 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 ReactFiberBeginWork + * @flow + */ + +'use strict'; + +import type { ReactCoroutine } from 'ReactCoroutine'; +import type { Fiber } from 'ReactFiber'; + +var ReactChildFiber = require('ReactChildFiber'); +var ReactTypesOfWork = require('ReactTypesOfWork'); +var { + IndeterminateComponent, + FunctionalComponent, + ClassComponent, + HostComponent, + CoroutineComponent, + YieldComponent, +} = ReactTypesOfWork; + +function getElement(unitOfWork) : ReactElement { + var element = unitOfWork.input; + if (!element) { + throw new Error('Should be resolved by now'); + } + return (element : ReactElement); +} + +function updateFunctionalComponent(unitOfWork) { + var element = getElement(unitOfWork); + var fn = element.type; + var props = element.props; + console.log('perform work on:', fn.name); + var nextChildren = fn(props); + + unitOfWork.child = ReactChildFiber.reconcileChildFibers( + unitOfWork, + unitOfWork.child, + nextChildren + ); +} + +function updateHostComponent(unitOfWork) { + var element = getElement(unitOfWork); + console.log('host component', element.type, typeof element.props.children === 'string' ? element.props.children : ''); + + var nextChildren = element.props.children; + unitOfWork.child = ReactChildFiber.reconcileChildFibers( + unitOfWork, + unitOfWork.child, + nextChildren + ); +} + +function mountIndeterminateComponent(unitOfWork) { + var element = getElement(unitOfWork); + var fn = element.type; + var props = element.props; + var value = fn(props); + if (typeof value === 'object' && value && typeof value.render === 'function') { + console.log('performed work on class:', fn.name); + // Proceed under the assumption that this is a class instance + unitOfWork.tag = ClassComponent; + } else { + console.log('performed work on fn:', fn.name); + // Proceed under the assumption that this is a functional component + unitOfWork.tag = FunctionalComponent; + } + unitOfWork.child = ReactChildFiber.reconcileChildFibers( + unitOfWork, + unitOfWork.child, + value + ); +} + +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); + break; + case FunctionalComponent: + updateFunctionalComponent(unitOfWork); + break; + case ClassComponent: + 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 new file mode 100644 index 0000000000000..827075f004312 --- /dev/null +++ b/src/renderers/shared/fiber/ReactFiberCompleteWork.js @@ -0,0 +1,121 @@ +/** + * 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 ReactFiberCompleteWork + * @flow + */ + +'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 { + IndeterminateComponent, + 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: + console.log('/functional component', unitOfWork.input.type.name); + transferOutput(unitOfWork.child, unitOfWork); + break; + case ClassComponent: + console.log('/class component', unitOfWork.input.type.name); + transferOutput(unitOfWork.child, unitOfWork); + break; + case HostComponent: + 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: + throw new Error('Unknown unit of work tag'); + } + return null; +}; diff --git a/src/renderers/shared/fiber/ReactFiberFunctionalComponent.js b/src/renderers/shared/fiber/ReactFiberFunctionalComponent.js deleted file mode 100644 index 8c5ac48f8c06f..0000000000000 --- a/src/renderers/shared/fiber/ReactFiberFunctionalComponent.js +++ /dev/null @@ -1,45 +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 ReactFiberFunctionalComponent - * @flow - */ - -'use strict'; - -import type { Fiber } from 'ReactFiber'; -var createFiber = require('ReactFiber'); - -var ReactTypesOfWork = require('ReactTypesOfWork'); -var { - FunctionalComponent, -} = ReactTypesOfWork; - -exports.performWork = function(unitOfWork : Fiber) : ?Fiber { - var element = unitOfWork.input; - if (!element) { - throw new Error('Should be resolved by now'); - } - var fn = element.type; - var props = element.props; - // console.log('perform work on:', fn.name); - var nextElement = fn(props); - - if (typeof nextElement.type === 'function') { - return exports.createFiber(nextElement); - } - return null; -}; - -exports.createFiber = function(element : ReactElement) { - var fiber = createFiber( - FunctionalComponent - ); - fiber.input = element; - return fiber; -}; diff --git a/src/renderers/shared/fiber/ReactFiberReconciler.js b/src/renderers/shared/fiber/ReactFiberReconciler.js index 3ef59bdb5e163..4a13a6531bca5 100644 --- a/src/renderers/shared/fiber/ReactFiberReconciler.js +++ b/src/renderers/shared/fiber/ReactFiberReconciler.js @@ -13,14 +13,9 @@ 'use strict'; import type { Fiber } from 'ReactFiber'; -var ReactFiberFunctionalComponent = require('ReactFiberFunctionalComponent'); - -var ReactTypesOfWork = require('ReactTypesOfWork'); -var { - FunctionalComponent, - ClassComponent, - HostComponent, -} = ReactTypesOfWork; +var ReactFiber = require('ReactFiber'); +var { beginWork } = require('ReactFiberBeginWork'); +var { completeWork } = require('ReactFiberCompleteWork'); type ReactHostElement = { type: T, @@ -54,16 +49,34 @@ module.exports = function(config : HostConfig) : Reconciler { let nextUnitOfWork : ?Fiber = null; - function performUnitOfWork(unit : Fiber) : ?Fiber { - switch (unit.tag) { - case FunctionalComponent: - return ReactFiberFunctionalComponent.performWork(unit); - case ClassComponent: - break; - case HostComponent: - break; + function completeUnitOfWork(unitOfWork : Fiber) : ?Fiber { + while (true) { + var next = completeWork(unitOfWork); + if (next) { + // If completing this work spawned new work, do that next. + return next; + } else if (unitOfWork.sibling) { + // If there is more work to do in this parent, do that next. + return unitOfWork.sibling; + } else if (unitOfWork.parent) { + // If there's no more work in this parent. Complete the parent. + unitOfWork = unitOfWork.parent; + } else { + // If we're at the root, there's no more work to do. + return null; + } + } + } + + function performUnitOfWork(unitOfWork : Fiber) : ?Fiber { + var next = beginWork(unitOfWork); + if (next) { + // If this spawns new work, do that next. + return next; + } else { + // Otherwise, complete the current work. + return completeUnitOfWork(unitOfWork); } - return null; } function performLowPriWork(deadline : Deadline) { @@ -100,7 +113,7 @@ module.exports = function(config : HostConfig) : Reconciler { ensureLowPriIsScheduled(); - nextUnitOfWork = ReactFiberFunctionalComponent.createFiber(element); + nextUnitOfWork = ReactFiber.createFiberFromElement(element); return {}; }, 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/ReactTypesOfWork.js b/src/renderers/shared/fiber/ReactTypesOfWork.js index 5dda9346102f0..5009cfa6bcb5b 100644 --- a/src/renderers/shared/fiber/ReactTypesOfWork.js +++ b/src/renderers/shared/fiber/ReactTypesOfWork.js @@ -13,9 +13,12 @@ 'use strict'; var TypesOfWork = { + IndeterminateComponent: 0, // Before we know whether it is functional or class FunctionalComponent: 1, ClassComponent: 2, HostComponent: 3, + CoroutineComponent: 4, + YieldComponent: 5, }; module.exports = TypesOfWork; 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); }); });