Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Fiber] Child Reconciler + New Coroutines Primitive #6859

Merged
merged 2 commits into from May 27, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually I guess this is a minor-version change as it allows objects as element types. Should we maybe do a backport of this and exclude this change? We might also be able to get away with not cherry-picking this at all…

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This only changes the warning behavior, but it would be nice if we could be a little more strict here. Forgetting to export anything from a CommonJS module lands you an empty object here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could add and Object.keys(type).length check.

Regardless we probably don't want this part in 15 since it will change the warning behavior (at least before if you had the case you talk about you know something is about to go wrong).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is going to be throwing later on if you tried to render this anywhere before. It is also going to be throwing through out version 15 so even if you try a later version and then downgrades, it'll still not warn. So I think this is pretty safe.

We can be more specific once we know a bit more about the data structures that will be allowed here. I suspect "module components" might end up here if we do those. As well as yields.

// 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
Copy link

@stryju stryju May 27, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

<3

element = ReactElement(fiber.input, null, element.ref, null, null, null, element.props);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this case for?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Never mind, I get it now. element.type comes in as object, so fiber is just element.type, and we apply the props to the continuation type.

}
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;
};