Skip to content

Commit

Permalink
Accept promise as element type (#13397)
Browse files Browse the repository at this point in the history
* Accept promise as element type

On the initial render, the element will suspend as if a promise were
thrown from inside the body of the unresolved component. Siblings should
continue rendering and if the parent is a Placeholder, the promise
should be captured by that Placeholder.

When the promise resolves, rendering resumes. If the resolved value
has a `default` property, it is assumed to be the default export of
an ES module, and we use that as the component type. If it does not have
a `default` property, we use the resolved value itself.

The resolved value is stored as an expando on the promise/thenable.

* Use special types of work for lazy components

Because reconciliation is a hot path, this adds ClassComponentLazy,
FunctionalComponentLazy, and ForwardRefLazy as special types of work.
The other types are not supported, but wouldn't be placed into a
separate module regardless.

* Resolve defaultProps for lazy types

* Remove some calls to isContextProvider

isContextProvider checks the fiber tag, but it's typically called after
we've already refined the type of work. We should get rid of it. I
removed some of them in the previous commit, and deleted a few more
in this one. I left a few behind because the remaining ones would
require additional refactoring that feels outside the scope of this PR.

* Remove getLazyComponentTypeIfResolved

* Return baseProps instead of null

The caller compares the result to baseProps to see if anything changed.

* Avoid redundant checks by inlining getFiberTagFromObjectType

* Move tag resolution to ReactFiber module

* Pass next props to update* functions

We should do this with all types of work in the future.

* Refine component type before pushing/popping context

Removes unnecessary checks.

* Replace all occurrences of _reactResult with helper

* Move shared thenable logic to `shared` package

* Check type of wrapper object before resolving to `default` export

* Return resolved tag instead of reassigning
  • Loading branch information
acdlite committed Aug 16, 2018
1 parent 77b7a66 commit 5031ebf
Show file tree
Hide file tree
Showing 24 changed files with 882 additions and 208 deletions.
6 changes: 5 additions & 1 deletion packages/react-dom/src/test-utils/ReactTestUtils.js
Expand Up @@ -11,7 +11,9 @@ import {findCurrentFiberUsingSlowPath} from 'react-reconciler/reflection';
import * as ReactInstanceMap from 'shared/ReactInstanceMap';
import {
ClassComponent,
ClassComponentLazy,
FunctionalComponent,
FunctionalComponentLazy,
HostComponent,
HostText,
} from 'shared/ReactTypeOfWork';
Expand Down Expand Up @@ -81,7 +83,9 @@ function findAllInRenderedFiberTreeInternal(fiber, test) {
node.tag === HostComponent ||
node.tag === HostText ||
node.tag === ClassComponent ||
node.tag === FunctionalComponent
node.tag === ClassComponentLazy ||
node.tag === FunctionalComponent ||
node.tag === FunctionalComponentLazy
) {
const publicInst = node.stateNode;
if (test(publicInst)) {
Expand Down
9 changes: 6 additions & 3 deletions packages/react-reconciler/src/ReactChildFiber.js
Expand Up @@ -23,6 +23,7 @@ import {
import {
FunctionalComponent,
ClassComponent,
ClassComponentLazy,
HostText,
HostPortal,
Fragment,
Expand Down Expand Up @@ -117,7 +118,7 @@ function coerceRef(
if (!didWarnAboutStringRefInStrictMode[componentName]) {
warningWithoutStack(
false,
'A string ref, "%s", has been found within a strict mode tree. ' +

This comment has been minimized.

Copy link
@gaearon

gaearon Aug 20, 2018

Collaborator

I don't understand what changed here. 🤔

This comment has been minimized.

Copy link
@NE-SmallTown

NE-SmallTown Aug 21, 2018

Contributor

IMO, In screen, yes, but in Unicode, no.

'A string ref, "%s", has been found within a strict mode tree. ' +
'String refs are a source of potential bugs and should be avoided. ' +
'We recommend using createRef() instead.' +
'\n%s' +
Expand All @@ -137,7 +138,8 @@ function coerceRef(
if (owner) {
const ownerFiber = ((owner: any): Fiber);
invariant(
ownerFiber.tag === ClassComponent,
ownerFiber.tag === ClassComponent ||
ownerFiber.tag === ClassComponentLazy,
'Stateless function components cannot have refs.',
);
inst = ownerFiber.stateNode;
Expand Down Expand Up @@ -1307,7 +1309,8 @@ function ChildReconciler(shouldTrackSideEffects) {
// component, throw an error. If Fiber return types are disabled,
// we already threw above.
switch (returnFiber.tag) {
case ClassComponent: {
case ClassComponent:
case ClassComponentLazy: {
if (__DEV__) {
const instance = returnFiber.stateNode;
if (instance.render._isMockFunction) {
Expand Down
4 changes: 4 additions & 0 deletions packages/react-reconciler/src/ReactCurrentFiber.js
Expand Up @@ -11,7 +11,9 @@ import ReactSharedInternals from 'shared/ReactSharedInternals';
import {
IndeterminateComponent,
FunctionalComponent,
FunctionalComponentLazy,
ClassComponent,
ClassComponentLazy,
HostComponent,
Mode,
} from 'shared/ReactTypeOfWork';
Expand All @@ -28,7 +30,9 @@ function describeFiber(fiber: Fiber): string {
switch (fiber.tag) {
case IndeterminateComponent:
case FunctionalComponent:
case FunctionalComponentLazy:
case ClassComponent:
case ClassComponentLazy:
case HostComponent:
case Mode:
const owner = fiber._debugOwner;
Expand Down
129 changes: 79 additions & 50 deletions packages/react-reconciler/src/ReactFiber.js
Expand Up @@ -33,6 +33,9 @@ import {
ContextConsumer,
Profiler,
PlaceholderComponent,
FunctionalComponentLazy,
ClassComponentLazy,
ForwardRefLazy,
} from 'shared/ReactTypeOfWork';
import getComponentName from 'shared/getComponentName';

Expand Down Expand Up @@ -278,8 +281,32 @@ const createFiber = function(
return new FiberNode(tag, pendingProps, key, mode);
};

function shouldConstruct(Component) {
return !!(Component.prototype && Component.prototype.isReactComponent);
function shouldConstruct(Component: Function) {
const prototype = Component.prototype;
return (
typeof prototype === 'object' &&
prototype !== null &&
typeof prototype.isReactComponent === 'object' &&
prototype.isReactComponent !== null
);
}

export function resolveLazyComponentTag(
fiber: Fiber,
Component: Function,
): void {
if (typeof Component === 'function') {
return shouldConstruct(Component)
? ClassComponentLazy
: FunctionalComponentLazy;
} else if (
Component !== undefined &&
Component !== null &&
Component.$$typeof
) {
return ForwardRefLazy;
}
return IndeterminateComponent;
}

// This is used to create an alternate fiber to do work on.
Expand Down Expand Up @@ -390,15 +417,15 @@ export function createFiberFromElement(
let fiber;
const type = element.type;
const key = element.key;
let pendingProps = element.props;
const pendingProps = element.props;

let fiberTag;
if (typeof type === 'function') {
fiberTag = shouldConstruct(type) ? ClassComponent : IndeterminateComponent;
} else if (typeof type === 'string') {
fiberTag = HostComponent;
} else {
switch (type) {
getTag: switch (type) {
case REACT_FRAGMENT_TYPE:
return createFiberFromFragment(
pendingProps.children,
Expand All @@ -419,9 +446,54 @@ export function createFiberFromElement(
case REACT_PLACEHOLDER_TYPE:
fiberTag = PlaceholderComponent;
break;
default:
fiberTag = getFiberTagFromObjectType(type, owner);
break;
default: {
if (typeof type === 'object' && type !== null) {
switch (type.$$typeof) {
case REACT_PROVIDER_TYPE:
fiberTag = ContextProvider;
break getTag;
case REACT_CONTEXT_TYPE:
// This is a consumer
fiberTag = ContextConsumer;
break getTag;
case REACT_FORWARD_REF_TYPE:
fiberTag = ForwardRef;
break getTag;
default: {
if (typeof type.then === 'function') {
fiberTag = IndeterminateComponent;
break getTag;
}
}
}
}
let info = '';
if (__DEV__) {
if (
type === undefined ||
(typeof type === 'object' &&
type !== null &&
Object.keys(type).length === 0)
) {
info +=
' You likely forgot to export your component from the file ' +
"it's defined in, or you might have mixed up default and " +
'named imports.';
}
const ownerName = owner ? getComponentName(owner.type) : null;
if (ownerName) {
info += '\n\nCheck the render method of `' + ownerName + '`.';
}
}
invariant(
false,
'Element type is invalid: expected a string (for built-in ' +
'components) or a class/function (for composite components) ' +
'but got: %s.%s',
type == null ? type : typeof type,
info,
);
}
}
}

Expand All @@ -437,49 +509,6 @@ export function createFiberFromElement(
return fiber;
}

function getFiberTagFromObjectType(type, owner): TypeOfWork {
const $$typeof =
typeof type === 'object' && type !== null ? type.$$typeof : null;

switch ($$typeof) {
case REACT_PROVIDER_TYPE:
return ContextProvider;
case REACT_CONTEXT_TYPE:
// This is a consumer
return ContextConsumer;
case REACT_FORWARD_REF_TYPE:
return ForwardRef;
default: {
let info = '';
if (__DEV__) {
if (
type === undefined ||
(typeof type === 'object' &&
type !== null &&
Object.keys(type).length === 0)
) {
info +=
' You likely forgot to export your component from the file ' +
"it's defined in, or you might have mixed up default and " +
'named imports.';
}
const ownerName = owner ? getComponentName(owner.type) : null;
if (ownerName) {
info += '\n\nCheck the render method of `' + ownerName + '`.';
}
}
invariant(
false,
'Element type is invalid: expected a string (for built-in ' +
'components) or a class/function (for composite components) ' +
'but got: %s.%s',
type == null ? type : typeof type,
info,
);
}
}
}

export function createFiberFromFragment(
elements: ReactFragment,
mode: TypeOfMode,
Expand Down

0 comments on commit 5031ebf

Please sign in to comment.