mirror of
https://github.com/facebook/react.git
synced 2026-02-27 03:07:57 +00:00
955 lines
30 KiB
JavaScript
955 lines
30 KiB
JavaScript
/**
|
||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||
*
|
||
* This source code is licensed under the MIT license found in the
|
||
* LICENSE file in the root directory of this source tree.
|
||
*
|
||
* @flow
|
||
*/
|
||
|
||
import type {ReactElement, Source} from 'shared/ReactElementType';
|
||
import type {
|
||
ReactFragment,
|
||
ReactPortal,
|
||
RefObject,
|
||
ReactEventResponder,
|
||
ReactEventResponderInstance,
|
||
ReactFundamentalComponent,
|
||
ReactScope,
|
||
} from 'shared/ReactTypes';
|
||
import type {RootTag} from 'shared/ReactRootTags';
|
||
import type {WorkTag} from 'shared/ReactWorkTags';
|
||
import type {TypeOfMode} from './ReactTypeOfMode';
|
||
import type {SideEffectTag} from 'shared/ReactSideEffectTags';
|
||
import type {ExpirationTime} from './ReactFiberExpirationTime';
|
||
import type {UpdateQueue} from './ReactUpdateQueue';
|
||
import type {ContextDependency} from './ReactFiberNewContext';
|
||
import type {HookType} from './ReactFiberHooks';
|
||
import type {SuspenseInstance} from './ReactFiberHostConfig';
|
||
|
||
import invariant from 'shared/invariant';
|
||
import warningWithoutStack from 'shared/warningWithoutStack';
|
||
import {
|
||
enableProfilerTimer,
|
||
enableFundamentalAPI,
|
||
enableUserTimingAPI,
|
||
enableScopeAPI,
|
||
} from 'shared/ReactFeatureFlags';
|
||
import {NoEffect, Placement} from 'shared/ReactSideEffectTags';
|
||
import {ConcurrentRoot, BatchedRoot} from 'shared/ReactRootTags';
|
||
import {
|
||
IndeterminateComponent,
|
||
ClassComponent,
|
||
HostRoot,
|
||
HostComponent,
|
||
HostText,
|
||
HostPortal,
|
||
ForwardRef,
|
||
Fragment,
|
||
Mode,
|
||
ContextProvider,
|
||
ContextConsumer,
|
||
Profiler,
|
||
SuspenseComponent,
|
||
SuspenseListComponent,
|
||
DehydratedFragment,
|
||
FunctionComponent,
|
||
MemoComponent,
|
||
SimpleMemoComponent,
|
||
LazyComponent,
|
||
FundamentalComponent,
|
||
ScopeComponent,
|
||
} from 'shared/ReactWorkTags';
|
||
import getComponentName from 'shared/getComponentName';
|
||
|
||
import {isDevToolsPresent} from './ReactFiberDevToolsHook';
|
||
import {
|
||
resolveClassForHotReloading,
|
||
resolveFunctionForHotReloading,
|
||
resolveForwardRefForHotReloading,
|
||
} from './ReactFiberHotReloading';
|
||
import {NoWork} from './ReactFiberExpirationTime';
|
||
import {
|
||
NoMode,
|
||
ConcurrentMode,
|
||
ProfileMode,
|
||
StrictMode,
|
||
BatchedMode,
|
||
} from './ReactTypeOfMode';
|
||
import {
|
||
REACT_FORWARD_REF_TYPE,
|
||
REACT_FRAGMENT_TYPE,
|
||
REACT_STRICT_MODE_TYPE,
|
||
REACT_PROFILER_TYPE,
|
||
REACT_PROVIDER_TYPE,
|
||
REACT_CONTEXT_TYPE,
|
||
REACT_CONCURRENT_MODE_TYPE,
|
||
REACT_SUSPENSE_TYPE,
|
||
REACT_SUSPENSE_LIST_TYPE,
|
||
REACT_MEMO_TYPE,
|
||
REACT_LAZY_TYPE,
|
||
REACT_FUNDAMENTAL_TYPE,
|
||
REACT_SCOPE_TYPE,
|
||
} from 'shared/ReactSymbols';
|
||
|
||
let hasBadMapPolyfill;
|
||
|
||
if (__DEV__) {
|
||
hasBadMapPolyfill = false;
|
||
try {
|
||
const nonExtensibleObject = Object.preventExtensions({});
|
||
const testMap = new Map([[nonExtensibleObject, null]]);
|
||
const testSet = new Set([nonExtensibleObject]);
|
||
// This is necessary for Rollup to not consider these unused.
|
||
// https://github.com/rollup/rollup/issues/1771
|
||
// TODO: we can remove these if Rollup fixes the bug.
|
||
testMap.set(0, 0);
|
||
testSet.add(0);
|
||
} catch (e) {
|
||
// TODO: Consider warning about bad polyfills
|
||
hasBadMapPolyfill = true;
|
||
}
|
||
}
|
||
|
||
export type Dependencies = {
|
||
expirationTime: ExpirationTime,
|
||
firstContext: ContextDependency<mixed> | null,
|
||
responders: Map<
|
||
ReactEventResponder<any, any>,
|
||
ReactEventResponderInstance<any, any>,
|
||
> | null,
|
||
};
|
||
|
||
// A Fiber is work on a Component that needs to be done or was done. There can
|
||
// be more than one per component.
|
||
export type Fiber = {|
|
||
// These first fields are conceptually members of an Instance. This used to
|
||
// be split into a separate type and intersected with the other Fiber fields,
|
||
// but until Flow fixes its intersection bugs, we've merged them into a
|
||
// single type.
|
||
|
||
// An Instance is shared between all versions of a component. We can easily
|
||
// break this out into a separate object to avoid copying so much to the
|
||
// alternate versions of the tree. We put this on a single object for now to
|
||
// minimize the number of objects created during the initial render.
|
||
|
||
// Tag identifying the type of fiber.
|
||
tag: WorkTag,
|
||
|
||
// Unique identifier of this child.
|
||
key: null | string,
|
||
|
||
// The value of element.type which is used to preserve the identity during
|
||
// reconciliation of this child.
|
||
elementType: any,
|
||
|
||
// The resolved function/class/ associated with this fiber.
|
||
type: any,
|
||
|
||
// The local state associated with this fiber.
|
||
stateNode: any,
|
||
|
||
// Conceptual aliases
|
||
// parent : Instance -> return The parent happens to be the same as the
|
||
// return fiber since we've merged the fiber and instance.
|
||
|
||
// Remaining fields belong to Fiber
|
||
|
||
// The Fiber to return to after finishing processing this one.
|
||
// This is effectively the parent, but there can be multiple parents (two)
|
||
// so this is only the parent of the thing we're currently processing.
|
||
// It is conceptually the same as the return address of a stack frame.
|
||
return: Fiber | null,
|
||
|
||
// Singly Linked List Tree Structure.
|
||
child: Fiber | null,
|
||
sibling: Fiber | null,
|
||
index: number,
|
||
|
||
// The ref last used to attach this node.
|
||
// I'll avoid adding an owner field for prod and model that as functions.
|
||
ref: null | (((handle: mixed) => void) & {_stringRef: ?string}) | RefObject,
|
||
|
||
// Input is the data coming into process this fiber. Arguments. Props.
|
||
pendingProps: any, // This type will be more specific once we overload the tag.
|
||
memoizedProps: any, // The props used to create the output.
|
||
|
||
// A queue of state updates and callbacks.
|
||
updateQueue: UpdateQueue<any> | null,
|
||
|
||
// The state used to create the output
|
||
memoizedState: any,
|
||
|
||
// Dependencies (contexts, events) for this fiber, if it has any
|
||
dependencies: Dependencies | null,
|
||
|
||
// Bitfield that describes properties about the fiber and its subtree. E.g.
|
||
// the ConcurrentMode flag indicates whether the subtree should be async-by-
|
||
// default. When a fiber is created, it inherits the mode of its
|
||
// parent. Additional flags can be set at creation time, but after that the
|
||
// value should remain unchanged throughout the fiber's lifetime, particularly
|
||
// before its child fibers are created.
|
||
mode: TypeOfMode,
|
||
|
||
// Effect
|
||
effectTag: SideEffectTag,
|
||
|
||
// Singly linked list fast path to the next fiber with side-effects.
|
||
nextEffect: Fiber | null,
|
||
|
||
// The first and last fiber with side-effect within this subtree. This allows
|
||
// us to reuse a slice of the linked list when we reuse the work done within
|
||
// this fiber.
|
||
firstEffect: Fiber | null,
|
||
lastEffect: Fiber | null,
|
||
|
||
// Represents a time in the future by which this work should be completed.
|
||
// Does not include work found in its subtree.
|
||
expirationTime: ExpirationTime,
|
||
|
||
// This is used to quickly determine if a subtree has no pending changes.
|
||
childExpirationTime: ExpirationTime,
|
||
|
||
// This is a pooled version of a Fiber. Every fiber that gets updated will
|
||
// eventually have a pair. There are cases when we can clean up pairs to save
|
||
// memory if we need to.
|
||
alternate: Fiber | null,
|
||
|
||
// Time spent rendering this Fiber and its descendants for the current update.
|
||
// This tells us how well the tree makes use of sCU for memoization.
|
||
// It is reset to 0 each time we render and only updated when we don't bailout.
|
||
// This field is only set when the enableProfilerTimer flag is enabled.
|
||
actualDuration?: number,
|
||
|
||
// If the Fiber is currently active in the "render" phase,
|
||
// This marks the time at which the work began.
|
||
// This field is only set when the enableProfilerTimer flag is enabled.
|
||
actualStartTime?: number,
|
||
|
||
// Duration of the most recent render time for this Fiber.
|
||
// This value is not updated when we bailout for memoization purposes.
|
||
// This field is only set when the enableProfilerTimer flag is enabled.
|
||
selfBaseDuration?: number,
|
||
|
||
// Sum of base times for all descendants of this Fiber.
|
||
// This value bubbles up during the "complete" phase.
|
||
// This field is only set when the enableProfilerTimer flag is enabled.
|
||
treeBaseDuration?: number,
|
||
|
||
// Conceptual aliases
|
||
// workInProgress : Fiber -> alternate The alternate used for reuse happens
|
||
// to be the same as work in progress.
|
||
// __DEV__ only
|
||
_debugID?: number,
|
||
_debugSource?: Source | null,
|
||
_debugOwner?: Fiber | null,
|
||
_debugIsCurrentlyTiming?: boolean,
|
||
_debugNeedsRemount?: boolean,
|
||
|
||
// Used to verify that the order of hooks does not change between renders.
|
||
_debugHookTypes?: Array<HookType> | null,
|
||
|};
|
||
|
||
let debugCounter = 1;
|
||
|
||
function FiberNode(
|
||
tag: WorkTag,
|
||
pendingProps: mixed,
|
||
key: null | string,
|
||
mode: TypeOfMode,
|
||
) {
|
||
// Instance
|
||
this.tag = tag;
|
||
this.key = key;
|
||
this.elementType = null;
|
||
this.type = null;
|
||
this.stateNode = null;
|
||
|
||
// Fiber
|
||
this.return = null;
|
||
this.child = null;
|
||
this.sibling = null;
|
||
this.index = 0;
|
||
|
||
this.ref = null;
|
||
|
||
this.pendingProps = pendingProps;
|
||
this.memoizedProps = null;
|
||
this.updateQueue = null;
|
||
this.memoizedState = null;
|
||
this.dependencies = null;
|
||
|
||
this.mode = mode;
|
||
|
||
// Effects
|
||
this.effectTag = NoEffect;
|
||
this.nextEffect = null;
|
||
|
||
this.firstEffect = null;
|
||
this.lastEffect = null;
|
||
|
||
this.expirationTime = NoWork;
|
||
this.childExpirationTime = NoWork;
|
||
|
||
this.alternate = null;
|
||
|
||
if (enableProfilerTimer) {
|
||
// Note: The following is done to avoid a v8 performance cliff.
|
||
//
|
||
// Initializing the fields below to smis and later updating them with
|
||
// double values will cause Fibers to end up having separate shapes.
|
||
// This behavior/bug has something to do with Object.preventExtension().
|
||
// Fortunately this only impacts DEV builds.
|
||
// Unfortunately it makes React unusably slow for some applications.
|
||
// To work around this, initialize the fields below with doubles.
|
||
//
|
||
// Learn more about this here:
|
||
// https://github.com/facebook/react/issues/14365
|
||
// https://bugs.chromium.org/p/v8/issues/detail?id=8538
|
||
this.actualDuration = Number.NaN;
|
||
this.actualStartTime = Number.NaN;
|
||
this.selfBaseDuration = Number.NaN;
|
||
this.treeBaseDuration = Number.NaN;
|
||
|
||
// It's okay to replace the initial doubles with smis after initialization.
|
||
// This won't trigger the performance cliff mentioned above,
|
||
// and it simplifies other profiler code (including DevTools).
|
||
this.actualDuration = 0;
|
||
this.actualStartTime = -1;
|
||
this.selfBaseDuration = 0;
|
||
this.treeBaseDuration = 0;
|
||
}
|
||
|
||
// This is normally DEV-only except www when it adds listeners.
|
||
// TODO: remove the User Timing integration in favor of Root Events.
|
||
if (enableUserTimingAPI) {
|
||
this._debugID = debugCounter++;
|
||
this._debugIsCurrentlyTiming = false;
|
||
}
|
||
|
||
if (__DEV__) {
|
||
this._debugSource = null;
|
||
this._debugOwner = null;
|
||
this._debugNeedsRemount = false;
|
||
this._debugHookTypes = null;
|
||
if (!hasBadMapPolyfill && typeof Object.preventExtensions === 'function') {
|
||
Object.preventExtensions(this);
|
||
}
|
||
}
|
||
}
|
||
|
||
// This is a constructor function, rather than a POJO constructor, still
|
||
// please ensure we do the following:
|
||
// 1) Nobody should add any instance methods on this. Instance methods can be
|
||
// more difficult to predict when they get optimized and they are almost
|
||
// never inlined properly in static compilers.
|
||
// 2) Nobody should rely on `instanceof Fiber` for type testing. We should
|
||
// always know when it is a fiber.
|
||
// 3) We might want to experiment with using numeric keys since they are easier
|
||
// to optimize in a non-JIT environment.
|
||
// 4) We can easily go from a constructor to a createFiber object literal if that
|
||
// is faster.
|
||
// 5) It should be easy to port this to a C struct and keep a C implementation
|
||
// compatible.
|
||
const createFiber = function(
|
||
tag: WorkTag,
|
||
pendingProps: mixed,
|
||
key: null | string,
|
||
mode: TypeOfMode,
|
||
): Fiber {
|
||
// $FlowFixMe: the shapes are exact here but Flow doesn't like constructors
|
||
return new FiberNode(tag, pendingProps, key, mode);
|
||
};
|
||
|
||
function shouldConstruct(Component: Function) {
|
||
const prototype = Component.prototype;
|
||
return !!(prototype && prototype.isReactComponent);
|
||
}
|
||
|
||
export function isSimpleFunctionComponent(type: any) {
|
||
return (
|
||
typeof type === 'function' &&
|
||
!shouldConstruct(type) &&
|
||
type.defaultProps === undefined
|
||
);
|
||
}
|
||
|
||
export function resolveLazyComponentTag(Component: Function): WorkTag {
|
||
if (typeof Component === 'function') {
|
||
return shouldConstruct(Component) ? ClassComponent : FunctionComponent;
|
||
} else if (Component !== undefined && Component !== null) {
|
||
const $$typeof = Component.$$typeof;
|
||
if ($$typeof === REACT_FORWARD_REF_TYPE) {
|
||
return ForwardRef;
|
||
}
|
||
if ($$typeof === REACT_MEMO_TYPE) {
|
||
return MemoComponent;
|
||
}
|
||
}
|
||
return IndeterminateComponent;
|
||
}
|
||
|
||
// This is used to create an alternate fiber to do work on.
|
||
export function createWorkInProgress(
|
||
current: Fiber,
|
||
pendingProps: any,
|
||
expirationTime: ExpirationTime,
|
||
): Fiber {
|
||
let workInProgress = current.alternate;
|
||
if (workInProgress === null) {
|
||
// We use a double buffering pooling technique because we know that we'll
|
||
// only ever need at most two versions of a tree. We pool the "other" unused
|
||
// node that we're free to reuse. This is lazily created to avoid allocating
|
||
// extra objects for things that are never updated. It also allow us to
|
||
// reclaim the extra memory if needed.
|
||
workInProgress = createFiber(
|
||
current.tag,
|
||
pendingProps,
|
||
current.key,
|
||
current.mode,
|
||
);
|
||
workInProgress.elementType = current.elementType;
|
||
workInProgress.type = current.type;
|
||
workInProgress.stateNode = current.stateNode;
|
||
|
||
if (__DEV__) {
|
||
// DEV-only fields
|
||
workInProgress._debugID = current._debugID;
|
||
workInProgress._debugSource = current._debugSource;
|
||
workInProgress._debugOwner = current._debugOwner;
|
||
workInProgress._debugHookTypes = current._debugHookTypes;
|
||
}
|
||
|
||
workInProgress.alternate = current;
|
||
current.alternate = workInProgress;
|
||
} else {
|
||
workInProgress.pendingProps = pendingProps;
|
||
|
||
// We already have an alternate.
|
||
// Reset the effect tag.
|
||
workInProgress.effectTag = NoEffect;
|
||
|
||
// The effect list is no longer valid.
|
||
workInProgress.nextEffect = null;
|
||
workInProgress.firstEffect = null;
|
||
workInProgress.lastEffect = null;
|
||
|
||
if (enableProfilerTimer) {
|
||
// We intentionally reset, rather than copy, actualDuration & actualStartTime.
|
||
// This prevents time from endlessly accumulating in new commits.
|
||
// This has the downside of resetting values for different priority renders,
|
||
// But works for yielding (the common case) and should support resuming.
|
||
workInProgress.actualDuration = 0;
|
||
workInProgress.actualStartTime = -1;
|
||
}
|
||
}
|
||
|
||
workInProgress.childExpirationTime = current.childExpirationTime;
|
||
workInProgress.expirationTime = current.expirationTime;
|
||
|
||
workInProgress.child = current.child;
|
||
workInProgress.memoizedProps = current.memoizedProps;
|
||
workInProgress.memoizedState = current.memoizedState;
|
||
workInProgress.updateQueue = current.updateQueue;
|
||
|
||
// Clone the dependencies object. This is mutated during the render phase, so
|
||
// it cannot be shared with the current fiber.
|
||
const currentDependencies = current.dependencies;
|
||
workInProgress.dependencies =
|
||
currentDependencies === null
|
||
? null
|
||
: {
|
||
expirationTime: currentDependencies.expirationTime,
|
||
firstContext: currentDependencies.firstContext,
|
||
responders: currentDependencies.responders,
|
||
};
|
||
|
||
// These will be overridden during the parent's reconciliation
|
||
workInProgress.sibling = current.sibling;
|
||
workInProgress.index = current.index;
|
||
workInProgress.ref = current.ref;
|
||
|
||
if (enableProfilerTimer) {
|
||
workInProgress.selfBaseDuration = current.selfBaseDuration;
|
||
workInProgress.treeBaseDuration = current.treeBaseDuration;
|
||
}
|
||
|
||
if (__DEV__) {
|
||
workInProgress._debugNeedsRemount = current._debugNeedsRemount;
|
||
switch (workInProgress.tag) {
|
||
case IndeterminateComponent:
|
||
case FunctionComponent:
|
||
case SimpleMemoComponent:
|
||
workInProgress.type = resolveFunctionForHotReloading(current.type);
|
||
break;
|
||
case ClassComponent:
|
||
workInProgress.type = resolveClassForHotReloading(current.type);
|
||
break;
|
||
case ForwardRef:
|
||
workInProgress.type = resolveForwardRefForHotReloading(current.type);
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
|
||
return workInProgress;
|
||
}
|
||
|
||
// Used to reuse a Fiber for a second pass.
|
||
export function resetWorkInProgress(
|
||
workInProgress: Fiber,
|
||
renderExpirationTime: ExpirationTime,
|
||
) {
|
||
// This resets the Fiber to what createFiber or createWorkInProgress would
|
||
// have set the values to before during the first pass. Ideally this wouldn't
|
||
// be necessary but unfortunately many code paths reads from the workInProgress
|
||
// when they should be reading from current and writing to workInProgress.
|
||
|
||
// We assume pendingProps, index, key, ref, return are still untouched to
|
||
// avoid doing another reconciliation.
|
||
|
||
// Reset the effect tag but keep any Placement tags, since that's something
|
||
// that child fiber is setting, not the reconciliation.
|
||
workInProgress.effectTag &= Placement;
|
||
|
||
// The effect list is no longer valid.
|
||
workInProgress.nextEffect = null;
|
||
workInProgress.firstEffect = null;
|
||
workInProgress.lastEffect = null;
|
||
|
||
let current = workInProgress.alternate;
|
||
if (current === null) {
|
||
// Reset to createFiber's initial values.
|
||
workInProgress.childExpirationTime = NoWork;
|
||
workInProgress.expirationTime = renderExpirationTime;
|
||
|
||
workInProgress.child = null;
|
||
workInProgress.memoizedProps = null;
|
||
workInProgress.memoizedState = null;
|
||
workInProgress.updateQueue = null;
|
||
|
||
workInProgress.dependencies = null;
|
||
|
||
if (enableProfilerTimer) {
|
||
// Note: We don't reset the actualTime counts. It's useful to accumulate
|
||
// actual time across multiple render passes.
|
||
workInProgress.selfBaseDuration = 0;
|
||
workInProgress.treeBaseDuration = 0;
|
||
}
|
||
} else {
|
||
// Reset to the cloned values that createWorkInProgress would've.
|
||
workInProgress.childExpirationTime = current.childExpirationTime;
|
||
workInProgress.expirationTime = current.expirationTime;
|
||
|
||
workInProgress.child = current.child;
|
||
workInProgress.memoizedProps = current.memoizedProps;
|
||
workInProgress.memoizedState = current.memoizedState;
|
||
workInProgress.updateQueue = current.updateQueue;
|
||
|
||
// Clone the dependencies object. This is mutated during the render phase, so
|
||
// it cannot be shared with the current fiber.
|
||
const currentDependencies = current.dependencies;
|
||
workInProgress.dependencies =
|
||
currentDependencies === null
|
||
? null
|
||
: {
|
||
expirationTime: currentDependencies.expirationTime,
|
||
firstContext: currentDependencies.firstContext,
|
||
responders: currentDependencies.responders,
|
||
};
|
||
|
||
if (enableProfilerTimer) {
|
||
// Note: We don't reset the actualTime counts. It's useful to accumulate
|
||
// actual time across multiple render passes.
|
||
workInProgress.selfBaseDuration = current.selfBaseDuration;
|
||
workInProgress.treeBaseDuration = current.treeBaseDuration;
|
||
}
|
||
}
|
||
|
||
return workInProgress;
|
||
}
|
||
|
||
export function createHostRootFiber(tag: RootTag): Fiber {
|
||
let mode;
|
||
if (tag === ConcurrentRoot) {
|
||
mode = ConcurrentMode | BatchedMode | StrictMode;
|
||
} else if (tag === BatchedRoot) {
|
||
mode = BatchedMode | StrictMode;
|
||
} else {
|
||
mode = NoMode;
|
||
}
|
||
|
||
if (enableProfilerTimer && isDevToolsPresent) {
|
||
// Always collect profile timings when DevTools are present.
|
||
// This enables DevTools to start capturing timing at any point–
|
||
// Without some nodes in the tree having empty base times.
|
||
mode |= ProfileMode;
|
||
}
|
||
|
||
return createFiber(HostRoot, null, null, mode);
|
||
}
|
||
|
||
export function createFiberFromTypeAndProps(
|
||
type: any, // React$ElementType
|
||
key: null | string,
|
||
pendingProps: any,
|
||
owner: null | Fiber,
|
||
mode: TypeOfMode,
|
||
expirationTime: ExpirationTime,
|
||
): Fiber {
|
||
let fiber;
|
||
|
||
let fiberTag = IndeterminateComponent;
|
||
// The resolved type is set if we know what the final type will be. I.e. it's not lazy.
|
||
let resolvedType = type;
|
||
if (typeof type === 'function') {
|
||
if (shouldConstruct(type)) {
|
||
fiberTag = ClassComponent;
|
||
if (__DEV__) {
|
||
resolvedType = resolveClassForHotReloading(resolvedType);
|
||
}
|
||
} else {
|
||
if (__DEV__) {
|
||
resolvedType = resolveFunctionForHotReloading(resolvedType);
|
||
}
|
||
}
|
||
} else if (typeof type === 'string') {
|
||
fiberTag = HostComponent;
|
||
} else {
|
||
getTag: switch (type) {
|
||
case REACT_FRAGMENT_TYPE:
|
||
return createFiberFromFragment(
|
||
pendingProps.children,
|
||
mode,
|
||
expirationTime,
|
||
key,
|
||
);
|
||
case REACT_CONCURRENT_MODE_TYPE:
|
||
fiberTag = Mode;
|
||
mode |= ConcurrentMode | BatchedMode | StrictMode;
|
||
break;
|
||
case REACT_STRICT_MODE_TYPE:
|
||
fiberTag = Mode;
|
||
mode |= StrictMode;
|
||
break;
|
||
case REACT_PROFILER_TYPE:
|
||
return createFiberFromProfiler(pendingProps, mode, expirationTime, key);
|
||
case REACT_SUSPENSE_TYPE:
|
||
return createFiberFromSuspense(pendingProps, mode, expirationTime, key);
|
||
case REACT_SUSPENSE_LIST_TYPE:
|
||
return createFiberFromSuspenseList(
|
||
pendingProps,
|
||
mode,
|
||
expirationTime,
|
||
key,
|
||
);
|
||
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;
|
||
if (__DEV__) {
|
||
resolvedType = resolveForwardRefForHotReloading(resolvedType);
|
||
}
|
||
break getTag;
|
||
case REACT_MEMO_TYPE:
|
||
fiberTag = MemoComponent;
|
||
break getTag;
|
||
case REACT_LAZY_TYPE:
|
||
fiberTag = LazyComponent;
|
||
resolvedType = null;
|
||
break getTag;
|
||
case REACT_FUNDAMENTAL_TYPE:
|
||
if (enableFundamentalAPI) {
|
||
return createFiberFromFundamental(
|
||
type,
|
||
pendingProps,
|
||
mode,
|
||
expirationTime,
|
||
key,
|
||
);
|
||
}
|
||
break;
|
||
case REACT_SCOPE_TYPE:
|
||
if (enableScopeAPI) {
|
||
return createFiberFromScope(
|
||
type,
|
||
pendingProps,
|
||
mode,
|
||
expirationTime,
|
||
key,
|
||
);
|
||
}
|
||
}
|
||
}
|
||
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,
|
||
);
|
||
}
|
||
}
|
||
}
|
||
|
||
fiber = createFiber(fiberTag, pendingProps, key, mode);
|
||
fiber.elementType = type;
|
||
fiber.type = resolvedType;
|
||
fiber.expirationTime = expirationTime;
|
||
|
||
return fiber;
|
||
}
|
||
|
||
export function createFiberFromElement(
|
||
element: ReactElement,
|
||
mode: TypeOfMode,
|
||
expirationTime: ExpirationTime,
|
||
): Fiber {
|
||
let owner = null;
|
||
if (__DEV__) {
|
||
owner = element._owner;
|
||
}
|
||
const type = element.type;
|
||
const key = element.key;
|
||
const pendingProps = element.props;
|
||
const fiber = createFiberFromTypeAndProps(
|
||
type,
|
||
key,
|
||
pendingProps,
|
||
owner,
|
||
mode,
|
||
expirationTime,
|
||
);
|
||
if (__DEV__) {
|
||
fiber._debugSource = element._source;
|
||
fiber._debugOwner = element._owner;
|
||
}
|
||
return fiber;
|
||
}
|
||
|
||
export function createFiberFromFragment(
|
||
elements: ReactFragment,
|
||
mode: TypeOfMode,
|
||
expirationTime: ExpirationTime,
|
||
key: null | string,
|
||
): Fiber {
|
||
const fiber = createFiber(Fragment, elements, key, mode);
|
||
fiber.expirationTime = expirationTime;
|
||
return fiber;
|
||
}
|
||
|
||
export function createFiberFromFundamental(
|
||
fundamentalComponent: ReactFundamentalComponent<any, any>,
|
||
pendingProps: any,
|
||
mode: TypeOfMode,
|
||
expirationTime: ExpirationTime,
|
||
key: null | string,
|
||
): Fiber {
|
||
const fiber = createFiber(FundamentalComponent, pendingProps, key, mode);
|
||
fiber.elementType = fundamentalComponent;
|
||
fiber.type = fundamentalComponent;
|
||
fiber.expirationTime = expirationTime;
|
||
return fiber;
|
||
}
|
||
|
||
function createFiberFromScope(
|
||
scope: ReactScope,
|
||
pendingProps: any,
|
||
mode: TypeOfMode,
|
||
expirationTime: ExpirationTime,
|
||
key: null | string,
|
||
) {
|
||
const fiber = createFiber(ScopeComponent, pendingProps, key, mode);
|
||
fiber.type = scope;
|
||
fiber.expirationTime = expirationTime;
|
||
return fiber;
|
||
}
|
||
|
||
function createFiberFromProfiler(
|
||
pendingProps: any,
|
||
mode: TypeOfMode,
|
||
expirationTime: ExpirationTime,
|
||
key: null | string,
|
||
): Fiber {
|
||
if (__DEV__) {
|
||
if (
|
||
typeof pendingProps.id !== 'string' ||
|
||
typeof pendingProps.onRender !== 'function'
|
||
) {
|
||
warningWithoutStack(
|
||
false,
|
||
'Profiler must specify an "id" string and "onRender" function as props',
|
||
);
|
||
}
|
||
}
|
||
|
||
const fiber = createFiber(Profiler, pendingProps, key, mode | ProfileMode);
|
||
// TODO: The Profiler fiber shouldn't have a type. It has a tag.
|
||
fiber.elementType = REACT_PROFILER_TYPE;
|
||
fiber.type = REACT_PROFILER_TYPE;
|
||
fiber.expirationTime = expirationTime;
|
||
|
||
return fiber;
|
||
}
|
||
|
||
export function createFiberFromSuspense(
|
||
pendingProps: any,
|
||
mode: TypeOfMode,
|
||
expirationTime: ExpirationTime,
|
||
key: null | string,
|
||
) {
|
||
const fiber = createFiber(SuspenseComponent, pendingProps, key, mode);
|
||
|
||
// TODO: The SuspenseComponent fiber shouldn't have a type. It has a tag.
|
||
// This needs to be fixed in getComponentName so that it relies on the tag
|
||
// instead.
|
||
fiber.type = REACT_SUSPENSE_TYPE;
|
||
fiber.elementType = REACT_SUSPENSE_TYPE;
|
||
|
||
fiber.expirationTime = expirationTime;
|
||
return fiber;
|
||
}
|
||
|
||
export function createFiberFromSuspenseList(
|
||
pendingProps: any,
|
||
mode: TypeOfMode,
|
||
expirationTime: ExpirationTime,
|
||
key: null | string,
|
||
) {
|
||
const fiber = createFiber(SuspenseListComponent, pendingProps, key, mode);
|
||
if (__DEV__) {
|
||
// TODO: The SuspenseListComponent fiber shouldn't have a type. It has a tag.
|
||
// This needs to be fixed in getComponentName so that it relies on the tag
|
||
// instead.
|
||
fiber.type = REACT_SUSPENSE_LIST_TYPE;
|
||
}
|
||
fiber.elementType = REACT_SUSPENSE_LIST_TYPE;
|
||
fiber.expirationTime = expirationTime;
|
||
return fiber;
|
||
}
|
||
|
||
export function createFiberFromText(
|
||
content: string,
|
||
mode: TypeOfMode,
|
||
expirationTime: ExpirationTime,
|
||
): Fiber {
|
||
const fiber = createFiber(HostText, content, null, mode);
|
||
fiber.expirationTime = expirationTime;
|
||
return fiber;
|
||
}
|
||
|
||
export function createFiberFromHostInstanceForDeletion(): Fiber {
|
||
const fiber = createFiber(HostComponent, null, null, NoMode);
|
||
// TODO: These should not need a type.
|
||
fiber.elementType = 'DELETED';
|
||
fiber.type = 'DELETED';
|
||
return fiber;
|
||
}
|
||
|
||
export function createFiberFromDehydratedFragment(
|
||
dehydratedNode: SuspenseInstance,
|
||
): Fiber {
|
||
const fiber = createFiber(DehydratedFragment, null, null, NoMode);
|
||
fiber.stateNode = dehydratedNode;
|
||
return fiber;
|
||
}
|
||
|
||
export function createFiberFromPortal(
|
||
portal: ReactPortal,
|
||
mode: TypeOfMode,
|
||
expirationTime: ExpirationTime,
|
||
): Fiber {
|
||
const pendingProps = portal.children !== null ? portal.children : [];
|
||
const fiber = createFiber(HostPortal, pendingProps, portal.key, mode);
|
||
fiber.expirationTime = expirationTime;
|
||
fiber.stateNode = {
|
||
containerInfo: portal.containerInfo,
|
||
pendingChildren: null, // Used by persistent updates
|
||
implementation: portal.implementation,
|
||
};
|
||
return fiber;
|
||
}
|
||
|
||
// Used for stashing WIP properties to replay failed work in DEV.
|
||
export function assignFiberPropertiesInDEV(
|
||
target: Fiber | null,
|
||
source: Fiber,
|
||
): Fiber {
|
||
if (target === null) {
|
||
// This Fiber's initial properties will always be overwritten.
|
||
// We only use a Fiber to ensure the same hidden class so DEV isn't slow.
|
||
target = createFiber(IndeterminateComponent, null, null, NoMode);
|
||
}
|
||
|
||
// This is intentionally written as a list of all properties.
|
||
// We tried to use Object.assign() instead but this is called in
|
||
// the hottest path, and Object.assign() was too slow:
|
||
// https://github.com/facebook/react/issues/12502
|
||
// This code is DEV-only so size is not a concern.
|
||
|
||
target.tag = source.tag;
|
||
target.key = source.key;
|
||
target.elementType = source.elementType;
|
||
target.type = source.type;
|
||
target.stateNode = source.stateNode;
|
||
target.return = source.return;
|
||
target.child = source.child;
|
||
target.sibling = source.sibling;
|
||
target.index = source.index;
|
||
target.ref = source.ref;
|
||
target.pendingProps = source.pendingProps;
|
||
target.memoizedProps = source.memoizedProps;
|
||
target.updateQueue = source.updateQueue;
|
||
target.memoizedState = source.memoizedState;
|
||
target.dependencies = source.dependencies;
|
||
target.mode = source.mode;
|
||
target.effectTag = source.effectTag;
|
||
target.nextEffect = source.nextEffect;
|
||
target.firstEffect = source.firstEffect;
|
||
target.lastEffect = source.lastEffect;
|
||
target.expirationTime = source.expirationTime;
|
||
target.childExpirationTime = source.childExpirationTime;
|
||
target.alternate = source.alternate;
|
||
if (enableProfilerTimer) {
|
||
target.actualDuration = source.actualDuration;
|
||
target.actualStartTime = source.actualStartTime;
|
||
target.selfBaseDuration = source.selfBaseDuration;
|
||
target.treeBaseDuration = source.treeBaseDuration;
|
||
}
|
||
target._debugID = source._debugID;
|
||
target._debugSource = source._debugSource;
|
||
target._debugOwner = source._debugOwner;
|
||
target._debugIsCurrentlyTiming = source._debugIsCurrentlyTiming;
|
||
target._debugNeedsRemount = source._debugNeedsRemount;
|
||
target._debugHookTypes = source._debugHookTypes;
|
||
return target;
|
||
}
|