Skip to content

Commit

Permalink
Add react-call-return package (facebook#11364)
Browse files Browse the repository at this point in the history
  • Loading branch information
gaearon authored and Ethan-Arrowood committed Dec 8, 2017
1 parent f81fdd8 commit aaada92
Show file tree
Hide file tree
Showing 20 changed files with 341 additions and 289 deletions.
11 changes: 11 additions & 0 deletions packages/react-call-return/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# react-call-return

This is an experimental package for multi-pass rendering in React.

**Its API is not as stable as that of React, React Native, or React DOM, and does not follow the common versioning scheme.**

**Use it at your own risk.**

# API

See the test case in `src/__tests__/ReactCallReturn.js` for an example.
12 changes: 12 additions & 0 deletions packages/react-call-return/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/**
* Copyright (c) 2013-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/

'use strict';

module.exports = require('./src/ReactCallReturn');
7 changes: 7 additions & 0 deletions packages/react-call-return/npm/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
'use strict';

if (process.env.NODE_ENV === 'production') {
module.exports = require('./cjs/react-call-return.production.min.js');
} else {
module.exports = require('./cjs/react-call-return.development.js');
}
13 changes: 13 additions & 0 deletions packages/react-call-return/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"name": "react-call-return",
"description": "Experimental APIs for multi-pass rendering in React.",
"version": "0.1.0",
"repository": "facebook/react",
"dependencies": {
"fbjs": "^0.8.16",
"object-assign": "^4.1.1"
},
"peerDependencies": {
"react": "^16.0.0"
}
}
94 changes: 94 additions & 0 deletions packages/react-call-return/src/ReactCallReturn.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/

'use strict';

import type {ReactCall, ReactNodeList, ReactReturn} from 'shared/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_CALL_TYPE;
var REACT_RETURN_TYPE;
if (typeof Symbol === 'function' && Symbol.for) {
REACT_CALL_TYPE = Symbol.for('react.call');
REACT_RETURN_TYPE = Symbol.for('react.return');
} else {
REACT_CALL_TYPE = 0xeac8;
REACT_RETURN_TYPE = 0xeac9;
}

type CallHandler<T> = (props: T, returns: Array<mixed>) => ReactNodeList;

exports.unstable_createCall = function<T>(
children: mixed,
handler: CallHandler<T>,
props: T,
key: ?string = null,
): ReactCall {
var call = {
// This tag allow us to uniquely identify this as a React Call
$$typeof: REACT_CALL_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(call.props);
Object.freeze(call);
}
}

return call;
};

exports.unstable_createReturn = function(value: mixed): ReactReturn {
var returnNode = {
// This tag allow us to uniquely identify this as a React Return
$$typeof: REACT_RETURN_TYPE,
value: value,
};

if (__DEV__) {
// TODO: Add _store property for marking this as validated.
if (Object.freeze) {
Object.freeze(returnNode);
}
}

return returnNode;
};

/**
* Verifies the object is a call object.
*/
exports.unstable_isCall = function(object: mixed): boolean {
return (
typeof object === 'object' &&
object !== null &&
object.$$typeof === REACT_CALL_TYPE
);
};

/**
* Verifies the object is a return object.
*/
exports.unstable_isReturn = function(object: mixed): boolean {
return (
typeof object === 'object' &&
object !== null &&
object.$$typeof === REACT_RETURN_TYPE
);
};

exports.unstable_REACT_RETURN_TYPE = REACT_RETURN_TYPE;
exports.unstable_REACT_CALL_TYPE = REACT_CALL_TYPE;
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,14 @@

var React;
var ReactNoop;
var ReactCoroutine;
var ReactCallReturn;

describe('ReactCoroutine', () => {
describe('ReactCallReturn', () => {
beforeEach(() => {
jest.resetModules();
React = require('react');
ReactNoop = require('react-noop-renderer');
// TODO: can we express this test with only public API?
// TODO: direct imports like some-package/src/* are bad. Fix me.
ReactCoroutine = require('react-reconciler/src/ReactCoroutine');
ReactCallReturn = require('react-call-return');
});

function div(...children) {
Expand All @@ -32,7 +30,7 @@ describe('ReactCoroutine', () => {
return {type: 'span', children: [], prop};
}

it('should render a coroutine', () => {
it('should render a call', () => {
var ops = [];

function Continuation({isSame}) {
Expand All @@ -41,10 +39,10 @@ describe('ReactCoroutine', () => {
}

// An alternative API could mark Continuation as something that needs
// yielding. E.g. Continuation.yieldType = 123;
// returning. E.g. Continuation.returnType = 123;
function Child({bar}) {
ops.push(['Child', bar]);
return ReactCoroutine.createYield({
return ReactCallReturn.unstable_createReturn({
props: {
bar: bar,
},
Expand All @@ -57,20 +55,20 @@ describe('ReactCoroutine', () => {
return [<Child key="a" bar={true} />, <Child key="b" bar={false} />];
}

function HandleYields(props, yields) {
ops.push('HandleYields');
return yields.map((y, i) => (
function HandleReturns(props, returns) {
ops.push('HandleReturns');
return returns.map((y, i) => (
<y.continuation key={i} isSame={props.foo === y.props.bar} />
));
}

// An alternative API could mark Parent as something that needs
// yielding. E.g. Parent.handler = HandleYields;
// returning. E.g. Parent.handler = HandleReturns;
function Parent(props) {
ops.push('Parent');
return ReactCoroutine.createCoroutine(
return ReactCallReturn.unstable_createCall(
props.children,
HandleYields,
HandleReturns,
props,
);
}
Expand All @@ -86,11 +84,11 @@ describe('ReactCoroutine', () => {
'Parent',
'Indirection',
['Child', true],
// Yield
// Return
['Child', false],
// Yield
'HandleYields',
// Continue yields
// Return
'HandleReturns',
// Call continuations
['Continuation', true],
['Continuation', false],
]);
Expand All @@ -99,13 +97,13 @@ describe('ReactCoroutine', () => {
]);
});

it('should update a coroutine', () => {
it('should update a call', () => {
function Continuation({isSame}) {
return <span prop={isSame ? 'foo==bar' : 'foo!=bar'} />;
}

function Child({bar}) {
return ReactCoroutine.createYield({
return ReactCallReturn.unstable_createReturn({
props: {
bar: bar,
},
Expand All @@ -117,16 +115,16 @@ describe('ReactCoroutine', () => {
return [<Child key="a" bar={true} />, <Child key="b" bar={false} />];
}

function HandleYields(props, yields) {
return yields.map((y, i) => (
function HandleReturns(props, returns) {
return returns.map((y, i) => (
<y.continuation key={i} isSame={props.foo === y.props.bar} />
));
}

function Parent(props) {
return ReactCoroutine.createCoroutine(
return ReactCallReturn.unstable_createCall(
props.children,
HandleYields,
HandleReturns,
props,
);
}
Expand All @@ -148,7 +146,7 @@ describe('ReactCoroutine', () => {
]);
});

it('should unmount a composite in a coroutine', () => {
it('should unmount a composite in a call', () => {
var ops = [];

class Continuation extends React.Component {
Expand All @@ -164,26 +162,26 @@ describe('ReactCoroutine', () => {
class Child extends React.Component {
render() {
ops.push('Child');
return ReactCoroutine.createYield(Continuation);
return ReactCallReturn.unstable_createReturn(Continuation);
}
componentWillUnmount() {
ops.push('Unmount Child');
}
}

function HandleYields(props, yields) {
ops.push('HandleYields');
return yields.map((ContinuationComponent, i) => (
function HandleReturns(props, returns) {
ops.push('HandleReturns');
return returns.map((ContinuationComponent, i) => (
<ContinuationComponent key={i} />
));
}

class Parent extends React.Component {
render() {
ops.push('Parent');
return ReactCoroutine.createCoroutine(
return ReactCallReturn.unstable_createCall(
this.props.children,
HandleYields,
HandleReturns,
this.props,
);
}
Expand All @@ -195,7 +193,7 @@ describe('ReactCoroutine', () => {
ReactNoop.render(<Parent><Child /></Parent>);
ReactNoop.flush();

expect(ops).toEqual(['Parent', 'Child', 'HandleYields', 'Continuation']);
expect(ops).toEqual(['Parent', 'Child', 'HandleReturns', 'Continuation']);

ops = [];

Expand All @@ -209,25 +207,25 @@ describe('ReactCoroutine', () => {
]);
});

it('should handle deep updates in coroutine', () => {
it('should handle deep updates in call', () => {
let instances = {};

class Counter extends React.Component {
state = {value: 5};
render() {
instances[this.props.id] = this;
return ReactCoroutine.createYield(this.state.value);
return ReactCallReturn.unstable_createReturn(this.state.value);
}
}

function App(props) {
return ReactCoroutine.createCoroutine(
return ReactCallReturn.unstable_createCall(
[
<Counter key="a" id="a" />,
<Counter key="b" id="b" />,
<Counter key="c" id="c" />,
],
(p, yields) => yields.map((y, i) => <span key={i} prop={y * 100} />),
(p, returns) => returns.map((y, i) => <span key={i} prop={y * 100} />),
{},
);
}
Expand Down
Loading

0 comments on commit aaada92

Please sign in to comment.