Skip to content

Commit

Permalink
Call and Return components should use ReactElement
Browse files Browse the repository at this point in the history
ReactChildFiber contains lots of branches that do the same thing for
different child types. We can unify them by having more child types be
ReactElements. This requires that the `type` and `key` fields are
sufficient to determine the identity of the child.

The main benefit is decreased file size, especially as we add more
component types, like context providers and consumers.

This updates Call and Return components to use ReactElement. Portals are
left alone for now because their identity includes the host instance.
  • Loading branch information
acdlite committed Dec 12, 2017
1 parent 7299238 commit d0bae7a
Show file tree
Hide file tree
Showing 11 changed files with 165 additions and 344 deletions.
45 changes: 29 additions & 16 deletions packages/react-call-return/src/ReactCallReturn.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,33 @@
* @flow
*/

import {REACT_CALL_TYPE, REACT_RETURN_TYPE} from 'shared/ReactSymbols';
import {
REACT_CALL_TYPE,
REACT_RETURN_TYPE,
REACT_ELEMENT_TYPE,
} from 'shared/ReactSymbols';

import type {ReactCall, ReactNodeList, ReactReturn} from 'shared/ReactTypes';

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

export function unstable_createCall<T>(
children: mixed,
handler: CallHandler<T>,
export function unstable_createCall<T, V>(
children: ReactNodeList,
handler: CallHandler<T, V>,
props: T,
key: ?string = null,
): ReactCall {
): ReactCall<V> {
const call = {
// This tag allow us to uniquely identify this as a React Call
$$typeof: REACT_CALL_TYPE,
$$typeof: REACT_ELEMENT_TYPE,
type: REACT_CALL_TYPE,
key: key == null ? null : '' + key,
children: children,
handler: handler,
props: props,
ref: null,
props: {
props,
handler,
children: children,
},
};

if (__DEV__) {
Expand All @@ -39,11 +47,16 @@ export function unstable_createCall<T>(
return call;
}

export function unstable_createReturn(value: mixed): ReactReturn {
export function unstable_createReturn<V>(value: V): ReactReturn<V> {
const returnNode = {
// This tag allow us to uniquely identify this as a React Return
$$typeof: REACT_RETURN_TYPE,
value: value,
// This tag allow us to uniquely identify this as a React Call
$$typeof: REACT_ELEMENT_TYPE,
type: REACT_RETURN_TYPE,
key: null,
ref: null,
props: {
value,
},
};

if (__DEV__) {
Expand All @@ -63,7 +76,7 @@ export function unstable_isCall(object: mixed): boolean {
return (
typeof object === 'object' &&
object !== null &&
object.$$typeof === REACT_CALL_TYPE
object.type === REACT_CALL_TYPE
);
}

Expand All @@ -74,7 +87,7 @@ export function unstable_isReturn(object: mixed): boolean {
return (
typeof object === 'object' &&
object !== null &&
object.$$typeof === REACT_RETURN_TYPE
object.type === REACT_RETURN_TYPE
);
}

Expand Down
11 changes: 6 additions & 5 deletions packages/react-dom/src/server/ReactPartialRenderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -621,11 +621,6 @@ class ReactDOMServerRenderer {
'Portals are not currently supported by the server renderer. ' +
'Render them conditionally so that they only appear on the client render.',
);
invariant(
$$typeof !== REACT_CALL_TYPE && $$typeof !== REACT_RETURN_TYPE,
'The experimental Call and Return types are not currently ' +
'supported by the server renderer.',
);
// Catch-all to prevent an infinite loop if React.Children.toArray() supports some new type.
invariant(
false,
Expand Down Expand Up @@ -678,6 +673,12 @@ class ReactDOMServerRenderer {
context: Object,
parentNamespace: string,
): string {
invariant(
element.type !== REACT_CALL_TYPE && element.type !== REACT_RETURN_TYPE,
'The experimental Call and Return types are not currently ' +
'supported by the server renderer.',
);

const tag = element.type.toLowerCase();

let namespace = parentNamespace;
Expand Down
225 changes: 2 additions & 223 deletions packages/react-reconciler/src/ReactChildFiber.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ import {
getIteratorFn,
REACT_ELEMENT_TYPE,
REACT_FRAGMENT_TYPE,
REACT_CALL_TYPE,
REACT_RETURN_TYPE,
REACT_PORTAL_TYPE,
} from 'shared/ReactSymbols';
import {
Expand Down Expand Up @@ -363,55 +361,6 @@ function ChildReconciler(shouldTrackSideEffects) {
}
}

function updateCall(
returnFiber: Fiber,
current: Fiber | null,
call: ReactCall,
expirationTime: ExpirationTime,
): Fiber {
// TODO: Should this also compare handler to determine whether to reuse?
if (current === null || current.tag !== CallComponent) {
// Insert
const created = createFiberFromCall(
call,
returnFiber.internalContextTag,
expirationTime,
);
created.return = returnFiber;
return created;
} else {
// Move based on index
const existing = useFiber(current, call, expirationTime);
existing.return = returnFiber;
return existing;
}
}

function updateReturn(
returnFiber: Fiber,
current: Fiber | null,
returnNode: ReactReturn,
expirationTime: ExpirationTime,
): Fiber {
if (current === null || current.tag !== ReturnComponent) {
// Insert
const created = createFiberFromReturn(
returnNode,
returnFiber.internalContextTag,
expirationTime,
);
created.type = returnNode.value;
created.return = returnFiber;
return created;
} else {
// Move based on index
const existing = useFiber(current, null, expirationTime);
existing.type = returnNode.value;
existing.return = returnFiber;
return existing;
}
}

function updatePortal(
returnFiber: Fiber,
current: Fiber | null,
Expand Down Expand Up @@ -486,48 +435,15 @@ function ChildReconciler(shouldTrackSideEffects) {
if (typeof newChild === 'object' && newChild !== null) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE: {
if (newChild.type === REACT_FRAGMENT_TYPE) {
const created = createFiberFromFragment(
newChild.props.children,
returnFiber.internalContextTag,
expirationTime,
newChild.key,
);
created.return = returnFiber;
return created;
} else {
const created = createFiberFromElement(
newChild,
returnFiber.internalContextTag,
expirationTime,
);
created.ref = coerceRef(null, newChild);
created.return = returnFiber;
return created;
}
}

case REACT_CALL_TYPE: {
const created = createFiberFromCall(
const created = createFiberFromElement(
newChild,
returnFiber.internalContextTag,
expirationTime,
);
created.ref = coerceRef(null, newChild);
created.return = returnFiber;
return created;
}

case REACT_RETURN_TYPE: {
const created = createFiberFromReturn(
newChild,
returnFiber.internalContextTag,
expirationTime,
);
created.type = newChild.value;
created.return = returnFiber;
return created;
}

case REACT_PORTAL_TYPE: {
const created = createFiberFromPortal(
newChild,
Expand Down Expand Up @@ -610,31 +526,6 @@ function ChildReconciler(shouldTrackSideEffects) {
return null;
}
}

case REACT_CALL_TYPE: {
if (newChild.key === key) {
return updateCall(returnFiber, oldFiber, newChild, expirationTime);
} else {
return null;
}
}

case REACT_RETURN_TYPE: {
// Returns don't have keys. If the previous node is implicitly keyed
// we can continue to replace it without aborting even if it is not a
// yield.
if (key === null) {
return updateReturn(
returnFiber,
oldFiber,
newChild,
expirationTime,
);
} else {
return null;
}
}

case REACT_PORTAL_TYPE: {
if (newChild.key === key) {
return updatePortal(
Expand Down Expand Up @@ -717,32 +608,6 @@ function ChildReconciler(shouldTrackSideEffects) {
expirationTime,
);
}

case REACT_CALL_TYPE: {
const matchedFiber =
existingChildren.get(
newChild.key === null ? newIdx : newChild.key,
) || null;
return updateCall(
returnFiber,
matchedFiber,
newChild,
expirationTime,
);
}

case REACT_RETURN_TYPE: {
// Returns don't have keys, so we neither have to check the old nor
// new node for the key. If both are returns, they match.
const matchedFiber = existingChildren.get(newIdx) || null;
return updateReturn(
returnFiber,
matchedFiber,
newChild,
expirationTime,
);
}

case REACT_PORTAL_TYPE: {
const matchedFiber =
existingChildren.get(
Expand Down Expand Up @@ -793,7 +658,6 @@ function ChildReconciler(shouldTrackSideEffects) {
}
switch (child.$$typeof) {
case REACT_ELEMENT_TYPE:
case REACT_CALL_TYPE:
case REACT_PORTAL_TYPE:
warnForMissingKey(child);
const key = child.key;
Expand Down Expand Up @@ -1252,72 +1116,6 @@ function ChildReconciler(shouldTrackSideEffects) {
}
}

function reconcileSingleCall(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
call: ReactCall,
expirationTime: ExpirationTime,
): Fiber {
const key = call.key;
let child = currentFirstChild;
while (child !== null) {
// TODO: If key === null and child.key === null, then this only applies to
// the first item in the list.
if (child.key === key) {
if (child.tag === CallComponent) {
deleteRemainingChildren(returnFiber, child.sibling);
const existing = useFiber(child, call, expirationTime);
existing.return = returnFiber;
return existing;
} else {
deleteRemainingChildren(returnFiber, child);
break;
}
} else {
deleteChild(returnFiber, child);
}
child = child.sibling;
}

const created = createFiberFromCall(
call,
returnFiber.internalContextTag,
expirationTime,
);
created.return = returnFiber;
return created;
}

function reconcileSingleReturn(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
returnNode: ReactReturn,
expirationTime: ExpirationTime,
): Fiber {
// There's no need to check for keys on yields since they're stateless.
let child = currentFirstChild;
if (child !== null) {
if (child.tag === ReturnComponent) {
deleteRemainingChildren(returnFiber, child.sibling);
const existing = useFiber(child, null, expirationTime);
existing.type = returnNode.value;
existing.return = returnFiber;
return existing;
} else {
deleteRemainingChildren(returnFiber, child);
}
}

const created = createFiberFromReturn(
returnNode,
returnFiber.internalContextTag,
expirationTime,
);
created.type = returnNode.value;
created.return = returnFiber;
return created;
}

function reconcileSinglePortal(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
Expand Down Expand Up @@ -1402,25 +1200,6 @@ function ChildReconciler(shouldTrackSideEffects) {
expirationTime,
),
);

case REACT_CALL_TYPE:
return placeSingleChild(
reconcileSingleCall(
returnFiber,
currentFirstChild,
newChild,
expirationTime,
),
);
case REACT_RETURN_TYPE:
return placeSingleChild(
reconcileSingleReturn(
returnFiber,
currentFirstChild,
newChild,
expirationTime,
),
);
case REACT_PORTAL_TYPE:
return placeSingleChild(
reconcileSinglePortal(
Expand Down
Loading

0 comments on commit d0bae7a

Please sign in to comment.