444 lines
12 KiB
JavaScript
444 lines
12 KiB
JavaScript
/**
|
|
* 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
|
|
*/
|
|
|
|
import type {Fiber} from './ReactFiber';
|
|
import type {FiberRoot} from './ReactFiberRoot';
|
|
import type {ReactNodeList} from 'shared/ReactTypes';
|
|
|
|
import {enableAsyncSubtreeAPI} from 'shared/ReactFeatureFlags';
|
|
import {
|
|
findCurrentHostFiber,
|
|
findCurrentHostFiberWithNoPortals,
|
|
} from 'shared/ReactFiberTreeReflection';
|
|
import * as ReactInstanceMap from 'shared/ReactInstanceMap';
|
|
import {HostComponent} from 'shared/ReactTypeOfWork';
|
|
import emptyObject from 'fbjs/lib/emptyObject';
|
|
import getComponentName from 'shared/getComponentName';
|
|
import warning from 'fbjs/lib/warning';
|
|
|
|
import {
|
|
findCurrentUnmaskedContext,
|
|
isContextProvider,
|
|
processChildContext,
|
|
} from './ReactFiberContext';
|
|
import {createFiberRoot} from './ReactFiberRoot';
|
|
import * as ReactFiberDevToolsHook from './ReactFiberDevToolsHook';
|
|
import ReactFiberScheduler from './ReactFiberScheduler';
|
|
import {insertUpdateIntoFiber} from './ReactFiberUpdateQueue';
|
|
import ReactFiberInstrumentation from './ReactFiberInstrumentation';
|
|
import ReactDebugCurrentFiber from './ReactDebugCurrentFiber';
|
|
|
|
if (__DEV__) {
|
|
var didWarnAboutNestedUpdates = false;
|
|
}
|
|
|
|
export type Deadline = {
|
|
timeRemaining: () => number,
|
|
};
|
|
|
|
type OpaqueHandle = Fiber;
|
|
type OpaqueRoot = FiberRoot;
|
|
|
|
export type HostConfig<T, P, I, TI, PI, C, CC, CX, PL> = {
|
|
getRootHostContext(rootContainerInstance: C): CX,
|
|
getChildHostContext(parentHostContext: CX, type: T, instance: C): CX,
|
|
getPublicInstance(instance: I | TI): PI,
|
|
|
|
createInstance(
|
|
type: T,
|
|
props: P,
|
|
rootContainerInstance: C,
|
|
hostContext: CX,
|
|
internalInstanceHandle: OpaqueHandle,
|
|
): I,
|
|
appendInitialChild(parentInstance: I, child: I | TI): void,
|
|
finalizeInitialChildren(
|
|
parentInstance: I,
|
|
type: T,
|
|
props: P,
|
|
rootContainerInstance: C,
|
|
): boolean,
|
|
|
|
prepareUpdate(
|
|
instance: I,
|
|
type: T,
|
|
oldProps: P,
|
|
newProps: P,
|
|
rootContainerInstance: C,
|
|
hostContext: CX,
|
|
): null | PL,
|
|
|
|
shouldSetTextContent(type: T, props: P): boolean,
|
|
shouldDeprioritizeSubtree(type: T, props: P): boolean,
|
|
|
|
createTextInstance(
|
|
text: string,
|
|
rootContainerInstance: C,
|
|
hostContext: CX,
|
|
internalInstanceHandle: OpaqueHandle,
|
|
): TI,
|
|
|
|
scheduleDeferredCallback(
|
|
callback: (deadline: Deadline) => void,
|
|
): number | void,
|
|
|
|
prepareForCommit(): void,
|
|
resetAfterCommit(): void,
|
|
|
|
now(): number,
|
|
|
|
useSyncScheduling?: boolean,
|
|
|
|
+hydration?: HydrationHostConfig<T, P, I, TI, C, CX, PL>,
|
|
|
|
+mutation?: MutableUpdatesHostConfig<T, P, I, TI, C, PL>,
|
|
+persistence?: PersistentUpdatesHostConfig<T, P, I, TI, C, CC, PL>,
|
|
};
|
|
|
|
type MutableUpdatesHostConfig<T, P, I, TI, C, PL> = {
|
|
commitUpdate(
|
|
instance: I,
|
|
updatePayload: PL,
|
|
type: T,
|
|
oldProps: P,
|
|
newProps: P,
|
|
internalInstanceHandle: OpaqueHandle,
|
|
): void,
|
|
commitMount(
|
|
instance: I,
|
|
type: T,
|
|
newProps: P,
|
|
internalInstanceHandle: OpaqueHandle,
|
|
): void,
|
|
commitTextUpdate(textInstance: TI, oldText: string, newText: string): void,
|
|
resetTextContent(instance: I): void,
|
|
appendChild(parentInstance: I, child: I | TI): void,
|
|
appendChildToContainer(container: C, child: I | TI): void,
|
|
insertBefore(parentInstance: I, child: I | TI, beforeChild: I | TI): void,
|
|
insertInContainerBefore(
|
|
container: C,
|
|
child: I | TI,
|
|
beforeChild: I | TI,
|
|
): void,
|
|
removeChild(parentInstance: I, child: I | TI): void,
|
|
removeChildFromContainer(container: C, child: I | TI): void,
|
|
};
|
|
|
|
type PersistentUpdatesHostConfig<T, P, I, TI, C, CC, PL> = {
|
|
cloneInstance(
|
|
instance: I,
|
|
updatePayload: null | PL,
|
|
type: T,
|
|
oldProps: P,
|
|
newProps: P,
|
|
internalInstanceHandle: OpaqueHandle,
|
|
keepChildren: boolean,
|
|
recyclableInstance: I,
|
|
): I,
|
|
|
|
createContainerChildSet(container: C): CC,
|
|
|
|
appendChildToContainerChildSet(childSet: CC, child: I | TI): void,
|
|
finalizeContainerChildren(container: C, newChildren: CC): void,
|
|
|
|
replaceContainerChildren(container: C, newChildren: CC): void,
|
|
};
|
|
|
|
type HydrationHostConfig<T, P, I, TI, C, CX, PL> = {
|
|
// Optional hydration
|
|
canHydrateInstance(instance: I | TI, type: T, props: P): boolean,
|
|
canHydrateTextInstance(instance: I | TI, text: string): boolean,
|
|
getNextHydratableSibling(instance: I | TI): null | I | TI,
|
|
getFirstHydratableChild(parentInstance: I | C): null | I | TI,
|
|
hydrateInstance(
|
|
instance: I,
|
|
type: T,
|
|
props: P,
|
|
rootContainerInstance: C,
|
|
hostContext: CX,
|
|
internalInstanceHandle: OpaqueHandle,
|
|
): null | PL,
|
|
hydrateTextInstance(
|
|
textInstance: TI,
|
|
text: string,
|
|
internalInstanceHandle: OpaqueHandle,
|
|
): boolean,
|
|
didNotMatchHydratedContainerTextInstance(
|
|
parentContainer: C,
|
|
textInstance: TI,
|
|
text: string,
|
|
): void,
|
|
didNotMatchHydratedTextInstance(
|
|
parentType: T,
|
|
parentProps: P,
|
|
parentInstance: I,
|
|
textInstance: TI,
|
|
text: string,
|
|
): void,
|
|
didNotHydrateContainerInstance(parentContainer: C, instance: I | TI): void,
|
|
didNotHydrateInstance(
|
|
parentType: T,
|
|
parentProps: P,
|
|
parentInstance: I,
|
|
instance: I | TI,
|
|
): void,
|
|
didNotFindHydratableContainerInstance(
|
|
parentContainer: C,
|
|
type: T,
|
|
props: P,
|
|
): void,
|
|
didNotFindHydratableContainerTextInstance(
|
|
parentContainer: C,
|
|
text: string,
|
|
): void,
|
|
didNotFindHydratableInstance(
|
|
parentType: T,
|
|
parentProps: P,
|
|
parentInstance: I,
|
|
type: T,
|
|
props: P,
|
|
): void,
|
|
didNotFindHydratableTextInstance(
|
|
parentType: T,
|
|
parentProps: P,
|
|
parentInstance: I,
|
|
text: string,
|
|
): void,
|
|
};
|
|
|
|
// 0 is PROD, 1 is DEV.
|
|
// Might add PROFILE later.
|
|
type BundleType = 0 | 1;
|
|
|
|
type DevToolsConfig<I, TI> = {|
|
|
bundleType: BundleType,
|
|
version: string,
|
|
rendererPackageName: string,
|
|
// Note: this actually *does* depend on Fiber internal fields.
|
|
// Used by "inspect clicked DOM element" in React DevTools.
|
|
findFiberByHostInstance?: (instance: I | TI) => Fiber,
|
|
// Used by RN in-app inspector.
|
|
// This API is unfortunately RN-specific.
|
|
// TODO: Change it to accept Fiber instead and type it properly.
|
|
getInspectorDataForViewTag?: (tag: number) => Object,
|
|
|};
|
|
|
|
export type Reconciler<C, I, TI> = {
|
|
createContainer(containerInfo: C, hydrate: boolean): OpaqueRoot,
|
|
updateContainer(
|
|
element: ReactNodeList,
|
|
container: OpaqueRoot,
|
|
parentComponent: ?React$Component<any, any>,
|
|
callback: ?Function,
|
|
): void,
|
|
batchedUpdates<A>(fn: () => A): A,
|
|
unbatchedUpdates<A>(fn: () => A): A,
|
|
flushSync<A>(fn: () => A): A,
|
|
deferredUpdates<A>(fn: () => A): A,
|
|
injectIntoDevTools(devToolsConfig: DevToolsConfig<I, TI>): boolean,
|
|
|
|
// Used to extract the return value from the initial render. Legacy API.
|
|
getPublicRootInstance(
|
|
container: OpaqueRoot,
|
|
): React$Component<any, any> | TI | I | null,
|
|
|
|
// Use for findDOMNode/findHostNode. Legacy API.
|
|
findHostInstance(component: Fiber): I | TI | null,
|
|
|
|
// Used internally for filtering out portals. Legacy API.
|
|
findHostInstanceWithNoPortals(component: Fiber): I | TI | null,
|
|
};
|
|
|
|
function getContextForSubtree(
|
|
parentComponent: ?React$Component<any, any>,
|
|
): Object {
|
|
if (!parentComponent) {
|
|
return emptyObject;
|
|
}
|
|
|
|
const fiber = ReactInstanceMap.get(parentComponent);
|
|
const parentContext = findCurrentUnmaskedContext(fiber);
|
|
return isContextProvider(fiber)
|
|
? processChildContext(fiber, parentContext)
|
|
: parentContext;
|
|
}
|
|
|
|
export default function<T, P, I, TI, PI, C, CC, CX, PL>(
|
|
config: HostConfig<T, P, I, TI, PI, C, CC, CX, PL>,
|
|
): Reconciler<C, I, TI> {
|
|
var {getPublicInstance} = config;
|
|
|
|
var {
|
|
computeAsyncExpiration,
|
|
computeExpirationForFiber,
|
|
scheduleWork,
|
|
batchedUpdates,
|
|
unbatchedUpdates,
|
|
flushSync,
|
|
deferredUpdates,
|
|
} = ReactFiberScheduler(config);
|
|
|
|
function scheduleTopLevelUpdate(
|
|
current: Fiber,
|
|
element: ReactNodeList,
|
|
callback: ?Function,
|
|
) {
|
|
if (__DEV__) {
|
|
if (
|
|
ReactDebugCurrentFiber.phase === 'render' &&
|
|
ReactDebugCurrentFiber.current !== null &&
|
|
!didWarnAboutNestedUpdates
|
|
) {
|
|
didWarnAboutNestedUpdates = true;
|
|
warning(
|
|
false,
|
|
'Render methods should be a pure function of props and state; ' +
|
|
'triggering nested component updates from render is not allowed. ' +
|
|
'If necessary, trigger nested updates in componentDidUpdate.\n\n' +
|
|
'Check the render method of %s.',
|
|
getComponentName(ReactDebugCurrentFiber.current) || 'Unknown',
|
|
);
|
|
}
|
|
}
|
|
|
|
callback = callback === undefined ? null : callback;
|
|
if (__DEV__) {
|
|
warning(
|
|
callback === null || typeof callback === 'function',
|
|
'render(...): Expected the last optional `callback` argument to be a ' +
|
|
'function. Instead received: %s.',
|
|
callback,
|
|
);
|
|
}
|
|
|
|
let expirationTime;
|
|
// Check if the top-level element is an async wrapper component. If so,
|
|
// treat updates to the root as async. This is a bit weird but lets us
|
|
// avoid a separate `renderAsync` API.
|
|
if (
|
|
enableAsyncSubtreeAPI &&
|
|
element != null &&
|
|
element.type != null &&
|
|
element.type.prototype != null &&
|
|
(element.type.prototype: any).unstable_isAsyncReactComponent === true
|
|
) {
|
|
expirationTime = computeAsyncExpiration();
|
|
} else {
|
|
expirationTime = computeExpirationForFiber(current);
|
|
}
|
|
|
|
const update = {
|
|
expirationTime,
|
|
partialState: {element},
|
|
callback,
|
|
isReplace: false,
|
|
isForced: false,
|
|
nextCallback: null,
|
|
next: null,
|
|
};
|
|
insertUpdateIntoFiber(current, update);
|
|
scheduleWork(current, expirationTime);
|
|
}
|
|
|
|
function findHostInstance(fiber: Fiber): PI | null {
|
|
const hostFiber = findCurrentHostFiber(fiber);
|
|
if (hostFiber === null) {
|
|
return null;
|
|
}
|
|
return hostFiber.stateNode;
|
|
}
|
|
|
|
return {
|
|
createContainer(containerInfo: C, hydrate: boolean): OpaqueRoot {
|
|
return createFiberRoot(containerInfo, hydrate);
|
|
},
|
|
|
|
updateContainer(
|
|
element: ReactNodeList,
|
|
container: OpaqueRoot,
|
|
parentComponent: ?React$Component<any, any>,
|
|
callback: ?Function,
|
|
): void {
|
|
// TODO: If this is a nested container, this won't be the root.
|
|
const current = container.current;
|
|
|
|
if (__DEV__) {
|
|
if (ReactFiberInstrumentation.debugTool) {
|
|
if (current.alternate === null) {
|
|
ReactFiberInstrumentation.debugTool.onMountContainer(container);
|
|
} else if (element === null) {
|
|
ReactFiberInstrumentation.debugTool.onUnmountContainer(container);
|
|
} else {
|
|
ReactFiberInstrumentation.debugTool.onUpdateContainer(container);
|
|
}
|
|
}
|
|
}
|
|
|
|
const context = getContextForSubtree(parentComponent);
|
|
if (container.context === null) {
|
|
container.context = context;
|
|
} else {
|
|
container.pendingContext = context;
|
|
}
|
|
|
|
scheduleTopLevelUpdate(current, element, callback);
|
|
},
|
|
|
|
batchedUpdates,
|
|
|
|
unbatchedUpdates,
|
|
|
|
deferredUpdates,
|
|
|
|
flushSync,
|
|
|
|
getPublicRootInstance(
|
|
container: OpaqueRoot,
|
|
): React$Component<any, any> | PI | null {
|
|
const containerFiber = container.current;
|
|
if (!containerFiber.child) {
|
|
return null;
|
|
}
|
|
switch (containerFiber.child.tag) {
|
|
case HostComponent:
|
|
return getPublicInstance(containerFiber.child.stateNode);
|
|
default:
|
|
return containerFiber.child.stateNode;
|
|
}
|
|
},
|
|
|
|
findHostInstance,
|
|
|
|
findHostInstanceWithNoPortals(fiber: Fiber): PI | null {
|
|
const hostFiber = findCurrentHostFiberWithNoPortals(fiber);
|
|
if (hostFiber === null) {
|
|
return null;
|
|
}
|
|
return hostFiber.stateNode;
|
|
},
|
|
|
|
injectIntoDevTools(devToolsConfig: DevToolsConfig<I, TI>): boolean {
|
|
const {findFiberByHostInstance} = devToolsConfig;
|
|
return ReactFiberDevToolsHook.injectInternals({
|
|
...devToolsConfig,
|
|
findHostInstanceByFiber(fiber: Fiber): I | TI | null {
|
|
return findHostInstance(fiber);
|
|
},
|
|
findFiberByHostInstance(instance: I | TI): Fiber | null {
|
|
if (!findFiberByHostInstance) {
|
|
// Might not be implemented by the renderer.
|
|
return null;
|
|
}
|
|
return findFiberByHostInstance(instance);
|
|
},
|
|
});
|
|
},
|
|
};
|
|
}
|