mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Merge pull request #6859 from sebmarkbage/newreconciler
[Fiber] Child Reconciler + New Coroutines Primitive
This commit is contained in:
@@ -398,4 +398,6 @@ ReactElement.isValidElement = function(object) {
|
||||
);
|
||||
};
|
||||
|
||||
ReactElement.REACT_ELEMENT_TYPE = REACT_ELEMENT_TYPE;
|
||||
|
||||
module.exports = ReactElement;
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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 <div>Hello World</div>;
|
||||
return <span><div>Hello World</div></span>;
|
||||
}
|
||||
|
||||
function Foo() {
|
||||
return <Bar isBar={true} />;
|
||||
return [
|
||||
<Bar isBar={true} />,
|
||||
<Bar isBar={true} />,
|
||||
];
|
||||
}
|
||||
|
||||
ReactNoop.render(<Foo />);
|
||||
@@ -53,5 +59,47 @@ describe('ReactComponent', function() {
|
||||
// console.log('Done');
|
||||
});
|
||||
|
||||
it('should render a coroutine', function() {
|
||||
|
||||
function Continuation({ isSame }) {
|
||||
return <span>{isSame ? 'foo==bar' : 'foo!=bar'}</span>;
|
||||
}
|
||||
|
||||
// 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 [<Child bar={true} />, <Child bar={false} />];
|
||||
}
|
||||
|
||||
function HandleYields(props, yields) {
|
||||
return yields.map(y =>
|
||||
<y.continuation isSame={props.foo === y.props.bar} />
|
||||
);
|
||||
}
|
||||
|
||||
// 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 <div><Parent foo={true}><Indirection /></Parent></div>;
|
||||
}
|
||||
|
||||
ReactNoop.render(<App />);
|
||||
ReactNoop.flush();
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
@@ -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<ReifiedYield> = [];
|
||||
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;
|
||||
};
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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<T, P> = {
|
||||
type: T,
|
||||
@@ -54,16 +49,34 @@ module.exports = function<T, P, I>(config : HostConfig<T, P, I>) : 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<T, P, I>(config : HostConfig<T, P, I>) : Reconciler {
|
||||
|
||||
ensureLowPriIsScheduled();
|
||||
|
||||
nextUnitOfWork = ReactFiberFunctionalComponent.createFiber(element);
|
||||
nextUnitOfWork = ReactFiber.createFiberFromElement(element);
|
||||
|
||||
return {};
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
};
|
||||
@@ -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,
|
||||
};
|
||||
};
|
||||
@@ -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;
|
||||
|
||||
@@ -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<T> = (props: T, yields: Array<ReifiedYield>) => ReactNodeList;
|
||||
|
||||
export type ReactCoroutine = {
|
||||
$$typeof: Symbol | number,
|
||||
key: ?string,
|
||||
children: any,
|
||||
// This should be a more specific CoroutineHandler
|
||||
handler: (props: any, yields: Array<ReifiedYield>) => ReactNodeList,
|
||||
props: mixed,
|
||||
};
|
||||
export type ReactYield = {
|
||||
$$typeof: Symbol | number,
|
||||
key: ?string,
|
||||
props: Object,
|
||||
continuation: mixed
|
||||
};
|
||||
|
||||
exports.createCoroutine = function<T>(children : mixed, handler : CoroutineHandler<T>, 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;
|
||||
@@ -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<ReactNode>;
|
||||
|
||||
export type ReactNodeList = ReactEmpty | ReactNode;
|
||||
|
||||
export type ReactText = string | number;
|
||||
|
||||
export type ReactEmpty = null | void | boolean;
|
||||
@@ -280,14 +280,8 @@ describe('ReactComponent', function() {
|
||||
'or a class/function (for composite components) but got: null.'
|
||||
);
|
||||
|
||||
var Z = {};
|
||||
expect(() => ReactTestUtils.renderIntoDocument(<Z />)).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);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user