@samitouri / QOS-React / commits / fd8d5f7a8a

Simplify coroutines by making yields stateless

Coroutines was kind of broken because it tried to do reparenting and enabling state preservation to be passed along the coroutine. However, since we couldn't determine which Fiber was "current" on a reified yield this was kind of broken. This removes the "continuation" part of yields so they're basically just return values. It is still possible to do continuations by just passing simple functions or classes as part of the return value but they're not stateful. This means that we won't have reparenting, but I actually don't think we need it. There's another way to structure this by doing all the state in the first phase and then yielding a stateless representation of the result. This stateless representation of the tree can then be rendered in different (or even multiple) locations. Because we no longer have a stateful continuation, you may have noticed that this really no longer represent the "coroutine" concept. I will rename it in a follow up commit.

Sebastian Markbage committed Jan 20, 2017 at 23:17 UTC fd8d5f7a8acadda78238c612311c9bd7bde4cf66
7 files changed +45 -118
src/isomorphic/classic/element/ReactElementValidator.js
+1 -2
@@ -200,8 +200,7 @@ var ReactElementValidator = {
200 createElement: function(type, props, children) {
201 var validType =
202 typeof type === 'string' ||
203 - typeof type === 'function' ||
204 - type !== null && typeof type === 'object' && typeof type.tag === 'number';
203 + typeof type === 'function';
204 // We warn in this case but don't throw. We expect the element creation to
205 // succeed and there will likely be errors in render.
206 if (!validType) {
src/renderers/shared/fiber/ReactChildFiber.js
+20 -42
@@ -29,7 +29,6 @@ var {
29 } = require('ReactPortal');
30
31 var ReactFiber = require('ReactFiber');
32 -var ReactReifiedYield = require('ReactReifiedYield');
32 var ReactTypeOfSideEffect = require('ReactTypeOfSideEffect');
33 var ReactTypeOfWork = require('ReactTypeOfWork');
34
@@ -53,11 +52,6 @@ const {
52 createFiberFromPortal,
53 } = ReactFiber;
54
56 -const {
57 - createReifiedYield,
58 - createUpdatedReifiedYield,
59 -} = ReactReifiedYield;
60 -
55 const isArray = Array.isArray;
56
57 const {
@@ -316,21 +310,16 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
310 yieldNode : ReactYield,
311 priority : PriorityLevel
312 ) : Fiber {
319 - // TODO: Should this also compare continuation to determine whether to reuse?
313 if (current == null || current.tag !== YieldComponent) {
314 // Insert
322 - const reifiedYield = createReifiedYield(yieldNode);
315 const created = createFiberFromYield(yieldNode, priority);
324 - created.type = reifiedYield;
316 + created.type = yieldNode.value;
317 created.return = returnFiber;
318 return created;
319 } else {
320 // Move based on index
321 const existing = useFiber(current, priority);
330 - existing.type = createUpdatedReifiedYield(
331 - current.type,
332 - yieldNode
333 - );
322 + existing.type = yieldNode.value;
323 existing.return = returnFiber;
324 return existing;
325 }
@@ -411,9 +400,8 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
400 }
401
402 case REACT_YIELD_TYPE: {
414 - const reifiedYield = createReifiedYield(newChild);
403 const created = createFiberFromYield(newChild, priority);
416 - created.type = reifiedYield;
404 + created.type = newChild.value;
405 created.return = returnFiber;
406 return created;
407 }
@@ -474,7 +462,10 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
462 }
463
464 case REACT_YIELD_TYPE: {
477 - if (newChild.key === key) {
465 + // Yields doesn't have keys. If the previous node is implicitly keyed
466 + // we can continue to replace it without aborting even if it is not a
467 + // yield.
468 + if (key === null) {
469 return updateYield(returnFiber, oldFiber, newChild, priority);
470 } else {
471 return null;
@@ -527,9 +518,9 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
518 }
519
520 case REACT_YIELD_TYPE: {
530 - const matchedFiber = existingChildren.get(
531 - newChild.key === null ? newIdx : newChild.key
532 - ) || null;
521 + // Yields doesn't have keys, so we neither have to check the old nor
522 + // new node for the key. If both are yields, they match.
523 + const matchedFiber = existingChildren.get(newIdx) || null;
524 return updateYield(returnFiber, matchedFiber, newChild, priority);
525 }
526
@@ -561,7 +552,6 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
552 switch (child.$$typeof) {
553 case REACT_ELEMENT_TYPE:
554 case REACT_COROUTINE_TYPE:
564 - case REACT_YIELD_TYPE:
555 case REACT_PORTAL_TYPE:
556 const key = child.key;
557 if (typeof key !== 'string') {
@@ -1019,34 +1009,22 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
1009 yieldNode : ReactYield,
1010 priority : PriorityLevel
1011 ) : Fiber {
1022 - const key = yieldNode.key;
1012 + // There's no need to check for keys on yields since they're stateless.
1013 let child = currentFirstChild;
1024 - while (child) {
1025 - // TODO: If key === null and child.key === null, then this only applies to
1026 - // the first item in the list.
1027 - if (child.key === key) {
1028 - if (child.tag === YieldComponent) {
1029 - deleteRemainingChildren(returnFiber, child.sibling);
1030 - const existing = useFiber(child, priority);
1031 - existing.type = createUpdatedReifiedYield(
1032 - child.type,
1033 - yieldNode
1034 - );
1035 - existing.return = returnFiber;
1036 - return existing;
1037 - } else {
1038 - deleteRemainingChildren(returnFiber, child);
1039 - break;
1040 - }
1014 + if (child) {
1015 + if (child.tag === YieldComponent) {
1016 + deleteRemainingChildren(returnFiber, child.sibling);
1017 + const existing = useFiber(child, priority);
1018 + existing.type = yieldNode.value;
1019 + existing.return = returnFiber;
1020 + return existing;
1021 } else {
1042 - deleteChild(returnFiber, child);
1022 + deleteRemainingChildren(returnFiber, child);
1023 }
1044 - child = child.sibling;
1024 }
1025
1047 - const reifiedYield = createReifiedYield(yieldNode);
1026 const created = createFiberFromYield(yieldNode, priority);
1049 - created.type = reifiedYield;
1027 + created.type = yieldNode.value;
1028 created.return = returnFiber;
1029 return created;
1030 }
src/renderers/shared/fiber/ReactFiber.js
+1 -2
@@ -393,8 +393,7 @@ exports.createFiberFromCoroutine = function(coroutine : ReactCoroutine, priority
393 };
394
395 exports.createFiberFromYield = function(yieldNode : ReactYield, priorityLevel : PriorityLevel) : Fiber {
396 - const fiber = createFiber(YieldComponent, yieldNode.key);
397 - fiber.pendingProps = {};
396 + const fiber = createFiber(YieldComponent, null);
397 return fiber;
398 };
399
src/renderers/shared/fiber/ReactFiberCompleteWork.js
+2 -3
@@ -17,7 +17,6 @@ import type { Fiber } from 'ReactFiber';
17 import type { HostContext } from 'ReactFiberHostContext';
18 import type { FiberRoot } from 'ReactFiberRoot';
19 import type { HostConfig } from 'ReactFiberReconciler';
20 -import type { ReifiedYield } from 'ReactReifiedYield';
20
21 var { reconcileChildFibers } = require('ReactChildFiber');
22 var {
@@ -84,7 +83,7 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
83 workInProgress.effectTag |= Update;
84 }
85
87 - function appendAllYields(yields : Array<ReifiedYield>, workInProgress : Fiber) {
86 + function appendAllYields(yields : Array<mixed>, workInProgress : Fiber) {
87 let node = workInProgress.stateNode;
88 while (node) {
89 if (node.tag === HostComponent || node.tag === HostText ||
@@ -128,7 +127,7 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
127
128 // Build up the yields.
129 // TODO: Compare this to a generator or opaque helpers like Children.
131 - var yields : Array<ReifiedYield> = [];
130 + var yields : Array<mixed> = [];
131 appendAllYields(yields, workInProgress);
132 var fn = coroutine.handler;
133 var props = coroutine.props;
src/renderers/shared/fiber/ReactReifiedYield.js deleted
-47
@@ -1,47 +0,0 @@
1 -/**
2 - * Copyright 2013-present, Facebook, Inc.
3 - * All rights reserved.
4 - *
5 - * This source code is licensed under the BSD-style license found in the
6 - * LICENSE file in the root directory of this source tree. An additional grant
7 - * of patent rights can be found in the PATENTS file in the same directory.
8 - *
9 - * @providesModule ReactReifiedYield
10 - * @flow
11 - */
12 -
13 -'use strict';
14 -
15 -import type { ReactYield } from 'ReactCoroutine';
16 -import type { Fiber } from 'ReactFiber';
17 -
18 -var { createFiberFromElementType } = require('ReactFiber');
19 -
20 -export type ReifiedYield = { continuation: Fiber, props: Object };
21 -
22 -exports.createReifiedYield = function(yieldNode : ReactYield) : ReifiedYield {
23 - var fiber = createFiberFromElementType(
24 - yieldNode.continuation,
25 - yieldNode.key,
26 - null // debugOwner
27 - );
28 - return {
29 - continuation: fiber,
30 - props: yieldNode.props,
31 - };
32 -};
33 -
34 -exports.createUpdatedReifiedYield = function(previousYield : ReifiedYield, yieldNode : ReactYield) : ReifiedYield {
35 - var fiber = previousYield.continuation;
36 - if (fiber.type !== yieldNode.continuation) {
37 - fiber = createFiberFromElementType(
38 - yieldNode.continuation,
39 - yieldNode.key,
40 - null // debugOwner
41 - );
42 - }
43 - return {
44 - continuation: fiber,
45 - props: yieldNode.props,
46 - };
47 -};
src/renderers/shared/fiber/__tests__/ReactCoroutine-test.js
+7 -4
@@ -39,8 +39,11 @@ describe('ReactCoroutine', () => {
39 function Child({ bar }) {
40 ops.push(['Child', bar]);
41 return ReactCoroutine.createYield({
42 - bar: bar,
43 - }, Continuation, null);
42 + props: {
43 + bar: bar,
44 + },
45 + continuation: Continuation,
46 + });
47 }
48
49 function Indirection() {
@@ -103,7 +106,7 @@ describe('ReactCoroutine', () => {
106 class Child extends React.Component {
107 render() {
108 ops.push('Child');
106 - return ReactCoroutine.createYield({}, Continuation, null);
109 + return ReactCoroutine.createYield(Continuation);
110 }
111 componentWillUnmount() {
112 ops.push('Unmount Child');
@@ -112,7 +115,7 @@ describe('ReactCoroutine', () => {
115
116 function HandleYields(props, yields) {
117 ops.push('HandleYields');
115 - return yields.map(y => <y.continuation />);
118 + return yields.map(ContinuationComponent => <ContinuationComponent />);
119 }
120
121 class Parent extends React.Component {
src/renderers/shared/fiber/isomorphic/ReactCoroutine.js
+14 -18
@@ -16,30 +16,29 @@ import type { ReactNodeList } from 'ReactTypes';
16
17 // The Symbol used to tag the special React types. If there is no native Symbol
18 // nor polyfill, then a plain number is used for performance.
19 -var REACT_COROUTINE_TYPE =
20 - (typeof Symbol === 'function' && Symbol.for && Symbol.for('react.coroutine')) ||
21 - 0xeac8;
19 +var REACT_COROUTINE_TYPE;
20 +var REACT_YIELD_TYPE;
21 +if (typeof Symbol === 'function' && Symbol.for) {
22 + REACT_COROUTINE_TYPE = Symbol.for('react.coroutine');
23 + REACT_YIELD_TYPE = Symbol.for('react.yield');
24 +} else {
25 + REACT_COROUTINE_TYPE = 0xeac8;
26 + REACT_YIELD_TYPE = 0xeac9;
27 +}
28
23 -var REACT_YIELD_TYPE =
24 - (typeof Symbol === 'function' && Symbol.for && Symbol.for('react.yield')) ||
25 - 0xeac9;
26 -
27 -type ReifiedYield = { continuation: Object, props: Object };
28 -type CoroutineHandler<T> = (props: T, yields: Array<ReifiedYield>) => ReactNodeList;
29 +type CoroutineHandler<T> = (props: T, yields: Array<mixed>) => ReactNodeList;
30
31 export type ReactCoroutine = {
32 $$typeof: Symbol | number,
33 key: null | string,
34 children: any,
35 // This should be a more specific CoroutineHandler
35 - handler: (props: any, yields: Array<ReifiedYield>) => ReactNodeList,
36 + handler: (props: any, yields: Array<mixed>) => ReactNodeList,
37 props: any,
38 };
39 export type ReactYield = {
40 $$typeof: Symbol | number,
40 - key: null | string,
41 - props: Object,
42 - continuation: mixed
41 + value: mixed,
42 };
43
44 exports.createCoroutine = function<T>(
@@ -68,19 +67,16 @@ exports.createCoroutine = function<T>(
67 return coroutine;
68 };
69
71 -exports.createYield = function(props : mixed, continuation : mixed, key : ?string = null) {
70 +exports.createYield = function(value : mixed) : ReactYield {
71 var yieldNode = {
72 // This tag allow us to uniquely identify this as a React Yield
73 $$typeof: REACT_YIELD_TYPE,
75 - key: key == null ? null : '' + key,
76 - props: props,
77 - continuation: continuation,
74 + value: value,
75 };
76
77 if (__DEV__) {
78 // TODO: Add _store property for marking this as validated.
79 if (Object.freeze) {
83 - Object.freeze(yieldNode.props);
80 Object.freeze(yieldNode);
81 }
82 }