Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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