Skip to content

Commit

Permalink
Merge pull request facebook#6859 from sebmarkbage/newreconciler
Browse files Browse the repository at this point in the history
[Fiber] Child Reconciler + New Coroutines Primitive
  • Loading branch information
sebmarkbage committed May 27, 2016
2 parents 2636155 + fd4f74e commit 0f4a4df
Show file tree
Hide file tree
Showing 15 changed files with 714 additions and 109 deletions.
2 changes: 2 additions & 0 deletions src/isomorphic/classic/element/ReactElement.js
Expand Up @@ -398,4 +398,6 @@ ReactElement.isValidElement = function(object) {
);
};

ReactElement.REACT_ELEMENT_TYPE = REACT_ELEMENT_TYPE;

module.exports = ReactElement;
3 changes: 2 additions & 1 deletion src/isomorphic/classic/element/ReactElementValidator.js
Expand Up @@ -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(
Expand Down
52 changes: 50 additions & 2 deletions src/renderers/noop/__tests__/ReactNoop-test.js
Expand Up @@ -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() {
Expand All @@ -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 />);
Expand All @@ -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();

});

});
129 changes: 129 additions & 0 deletions 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);
};
83 changes: 70 additions & 13 deletions src/renderers/shared/fiber/ReactFiber.js
Expand Up @@ -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,
Expand All @@ -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 comments on commit 0f4a4df

Please sign in to comment.