mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
61f62246c8
* [react-native] Use path-based imports instead of Haste for the RN renderer To move React Native to standard path-based imports instead of Haste, the RN renderer that is generated from the code in this repo needs to use path-based imports as well since the generated code is vendored by RN. This commit makes it so the interface between the generated renderers and RN does not rely on Haste and instead uses a private interface explicitly defined by RN. This inverts control of the abstraction so that RN decides the internals to export rather than React deciding what to import. On RN's side, a new module named `react-native/Libraries/ReactPrivate/ReactNativePrivateInterface` explicitly exports the modules used by the renderers in this repo. (There is also a private module for InitializeCore so that we can import it just for the side effects.) On React's side, the various renderer modules access RN internals through the explicit private interface. The Rollup configuration becomes slimmer since the only external package is now `react-native`, and the individual modules are instead listed out in `ReactNativePrivateInterface`. Task description: https://github.com/facebook/react-native/issues/24770 Sister RN PR (needs to land before this one): https://github.com/facebook/react-native/pull/24782 Test Plan: Ran unit tests and Flow in this repo. Generated the renderers and manually copied them over to the RN repo. Ran the RN tests and launched the RNTester app. * Access natively defined "nativeFabricUIManager" instead of importing it Some places in the Fabric renderers access `nativeFabricUIManager` (a natively defined global) instead of importing UIManager. While this is coupling across repos that depends on the timing of events, it is necessary until we have a way to defer top-level imports to run after `nativeFabricUIManager` is defined. So for consistency we use `nativeFabricUIManager` everywhere (see the comment in https://github.com/facebook/react/pull/15604#pullrequestreview-236842223 for more context).
478 lines
12 KiB
JavaScript
478 lines
12 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 {
|
|
MeasureInWindowOnSuccessCallback,
|
|
MeasureLayoutOnSuccessCallback,
|
|
MeasureOnSuccessCallback,
|
|
NativeMethodsMixinType,
|
|
ReactNativeBaseComponentViewConfig,
|
|
} from './ReactNativeTypes';
|
|
import type {ReactEventComponentInstance} from 'shared/ReactTypes';
|
|
|
|
import {mountSafeCallback_NOT_REALLY_SAFE} from './NativeMethodsMixinUtils';
|
|
import {create, diff} from './ReactNativeAttributePayload';
|
|
|
|
import invariant from 'shared/invariant';
|
|
import warningWithoutStack from 'shared/warningWithoutStack';
|
|
|
|
import {dispatchEvent} from './ReactFabricEventEmitter';
|
|
|
|
// Modules provided by RN:
|
|
import {
|
|
ReactNativeViewConfigRegistry,
|
|
TextInputState,
|
|
deepFreezeAndThrowOnMutationInDev,
|
|
} from 'react-native/Libraries/ReactPrivate/ReactNativePrivateInterface';
|
|
|
|
const {
|
|
createNode,
|
|
cloneNode,
|
|
cloneNodeWithNewChildren,
|
|
cloneNodeWithNewChildrenAndProps,
|
|
cloneNodeWithNewProps,
|
|
createChildSet: createChildNodeSet,
|
|
appendChild: appendChildNode,
|
|
appendChildToSet: appendChildNodeToSet,
|
|
completeRoot,
|
|
registerEventHandler,
|
|
measure: fabricMeasure,
|
|
measureInWindow: fabricMeasureInWindow,
|
|
measureLayout: fabricMeasureLayout,
|
|
} = nativeFabricUIManager;
|
|
|
|
const {get: getViewConfigForType} = ReactNativeViewConfigRegistry;
|
|
|
|
// Counter for uniquely identifying views.
|
|
// % 10 === 1 means it is a rootTag.
|
|
// % 2 === 0 means it is a Fabric tag.
|
|
// This means that they never overlap.
|
|
let nextReactTag = 2;
|
|
|
|
type Node = Object;
|
|
export type Type = string;
|
|
export type Props = Object;
|
|
export type Instance = {
|
|
node: Node,
|
|
canonical: ReactFabricHostComponent,
|
|
};
|
|
export type TextInstance = {
|
|
node: Node,
|
|
};
|
|
export type HydratableInstance = Instance | TextInstance;
|
|
export type PublicInstance = ReactFabricHostComponent;
|
|
export type Container = number;
|
|
export type ChildSet = Object;
|
|
export type HostContext = $ReadOnly<{|
|
|
isInAParentText: boolean,
|
|
|}>;
|
|
export type UpdatePayload = Object;
|
|
|
|
export type TimeoutHandle = TimeoutID;
|
|
export type NoTimeout = -1;
|
|
|
|
// TODO: Remove this conditional once all changes have propagated.
|
|
if (registerEventHandler) {
|
|
/**
|
|
* Register the event emitter with the native bridge
|
|
*/
|
|
registerEventHandler(dispatchEvent);
|
|
}
|
|
|
|
/**
|
|
* This is used for refs on host components.
|
|
*/
|
|
class ReactFabricHostComponent {
|
|
_nativeTag: number;
|
|
viewConfig: ReactNativeBaseComponentViewConfig<>;
|
|
currentProps: Props;
|
|
_internalInstanceHandle: Object;
|
|
|
|
constructor(
|
|
tag: number,
|
|
viewConfig: ReactNativeBaseComponentViewConfig<>,
|
|
props: Props,
|
|
internalInstanceHandle: Object,
|
|
) {
|
|
this._nativeTag = tag;
|
|
this.viewConfig = viewConfig;
|
|
this.currentProps = props;
|
|
this._internalInstanceHandle = internalInstanceHandle;
|
|
}
|
|
|
|
blur() {
|
|
TextInputState.blurTextInput(this._nativeTag);
|
|
}
|
|
|
|
focus() {
|
|
TextInputState.focusTextInput(this._nativeTag);
|
|
}
|
|
|
|
measure(callback: MeasureOnSuccessCallback) {
|
|
fabricMeasure(
|
|
this._internalInstanceHandle.stateNode.node,
|
|
mountSafeCallback_NOT_REALLY_SAFE(this, callback),
|
|
);
|
|
}
|
|
|
|
measureInWindow(callback: MeasureInWindowOnSuccessCallback) {
|
|
fabricMeasureInWindow(
|
|
this._internalInstanceHandle.stateNode.node,
|
|
mountSafeCallback_NOT_REALLY_SAFE(this, callback),
|
|
);
|
|
}
|
|
|
|
measureLayout(
|
|
relativeToNativeNode: number | Object,
|
|
onSuccess: MeasureLayoutOnSuccessCallback,
|
|
onFail: () => void /* currently unused */,
|
|
) {
|
|
if (
|
|
typeof relativeToNativeNode === 'number' ||
|
|
!(relativeToNativeNode instanceof ReactFabricHostComponent)
|
|
) {
|
|
warningWithoutStack(
|
|
false,
|
|
'Warning: ref.measureLayout must be called with a ref to a native component.',
|
|
);
|
|
|
|
return;
|
|
}
|
|
|
|
fabricMeasureLayout(
|
|
this._internalInstanceHandle.stateNode.node,
|
|
relativeToNativeNode._internalInstanceHandle.stateNode.node,
|
|
mountSafeCallback_NOT_REALLY_SAFE(this, onFail),
|
|
mountSafeCallback_NOT_REALLY_SAFE(this, onSuccess),
|
|
);
|
|
}
|
|
|
|
setNativeProps(nativeProps: Object) {
|
|
warningWithoutStack(
|
|
false,
|
|
'Warning: setNativeProps is not currently supported in Fabric',
|
|
);
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
// eslint-disable-next-line no-unused-expressions
|
|
(ReactFabricHostComponent.prototype: NativeMethodsMixinType);
|
|
|
|
export * from 'shared/HostConfigWithNoMutation';
|
|
export * from 'shared/HostConfigWithNoHydration';
|
|
|
|
export function appendInitialChild(
|
|
parentInstance: Instance,
|
|
child: Instance | TextInstance,
|
|
): void {
|
|
appendChildNode(parentInstance.node, child.node);
|
|
}
|
|
|
|
export function createInstance(
|
|
type: string,
|
|
props: Props,
|
|
rootContainerInstance: Container,
|
|
hostContext: HostContext,
|
|
internalInstanceHandle: Object,
|
|
): Instance {
|
|
const tag = nextReactTag;
|
|
nextReactTag += 2;
|
|
|
|
const viewConfig = getViewConfigForType(type);
|
|
|
|
if (__DEV__) {
|
|
for (const key in viewConfig.validAttributes) {
|
|
if (props.hasOwnProperty(key)) {
|
|
deepFreezeAndThrowOnMutationInDev(props[key]);
|
|
}
|
|
}
|
|
}
|
|
|
|
const updatePayload = create(props, viewConfig.validAttributes);
|
|
|
|
const node = createNode(
|
|
tag, // reactTag
|
|
viewConfig.uiViewClassName, // viewName
|
|
rootContainerInstance, // rootTag
|
|
updatePayload, // props
|
|
internalInstanceHandle, // internalInstanceHandle
|
|
);
|
|
|
|
const component = new ReactFabricHostComponent(
|
|
tag,
|
|
viewConfig,
|
|
props,
|
|
internalInstanceHandle,
|
|
);
|
|
|
|
return {
|
|
node: node,
|
|
canonical: component,
|
|
};
|
|
}
|
|
|
|
export function createTextInstance(
|
|
text: string,
|
|
rootContainerInstance: Container,
|
|
hostContext: HostContext,
|
|
internalInstanceHandle: Object,
|
|
): TextInstance {
|
|
invariant(
|
|
hostContext.isInAParentText,
|
|
'Text strings must be rendered within a <Text> component.',
|
|
);
|
|
|
|
const tag = nextReactTag;
|
|
nextReactTag += 2;
|
|
|
|
const node = createNode(
|
|
tag, // reactTag
|
|
'RCTRawText', // viewName
|
|
rootContainerInstance, // rootTag
|
|
{text: text}, // props
|
|
internalInstanceHandle, // instance handle
|
|
);
|
|
|
|
return {
|
|
node: node,
|
|
};
|
|
}
|
|
|
|
export function finalizeInitialChildren(
|
|
parentInstance: Instance,
|
|
type: string,
|
|
props: Props,
|
|
rootContainerInstance: Container,
|
|
hostContext: HostContext,
|
|
): boolean {
|
|
return false;
|
|
}
|
|
|
|
export function getRootHostContext(
|
|
rootContainerInstance: Container,
|
|
): HostContext {
|
|
return {isInAParentText: false};
|
|
}
|
|
|
|
export function getChildHostContext(
|
|
parentHostContext: HostContext,
|
|
type: string,
|
|
rootContainerInstance: Container,
|
|
): HostContext {
|
|
const prevIsInAParentText = parentHostContext.isInAParentText;
|
|
const isInAParentText =
|
|
type === 'AndroidTextInput' || // Android
|
|
type === 'RCTMultilineTextInputView' || // iOS
|
|
type === 'RCTSinglelineTextInputView' || // iOS
|
|
type === 'RCTText' ||
|
|
type === 'RCTVirtualText';
|
|
|
|
if (prevIsInAParentText !== isInAParentText) {
|
|
return {isInAParentText};
|
|
} else {
|
|
return parentHostContext;
|
|
}
|
|
}
|
|
|
|
export function getChildHostContextForEventComponent(
|
|
parentHostContext: HostContext,
|
|
) {
|
|
// TODO: add getChildHostContextForEventComponent implementation
|
|
return parentHostContext;
|
|
}
|
|
|
|
export function getChildHostContextForEventTarget(
|
|
parentHostContext: HostContext,
|
|
type: Symbol | number,
|
|
) {
|
|
// TODO: add getChildHostContextForEventTarget implementation
|
|
return parentHostContext;
|
|
}
|
|
|
|
export function getPublicInstance(instance: Instance): * {
|
|
return instance.canonical;
|
|
}
|
|
|
|
export function prepareForCommit(containerInfo: Container): void {
|
|
// Noop
|
|
}
|
|
|
|
export function prepareUpdate(
|
|
instance: Instance,
|
|
type: string,
|
|
oldProps: Props,
|
|
newProps: Props,
|
|
rootContainerInstance: Container,
|
|
hostContext: HostContext,
|
|
): null | Object {
|
|
const viewConfig = instance.canonical.viewConfig;
|
|
const updatePayload = diff(oldProps, newProps, viewConfig.validAttributes);
|
|
// TODO: If the event handlers have changed, we need to update the current props
|
|
// in the commit phase but there is no host config hook to do it yet.
|
|
// So instead we hack it by updating it in the render phase.
|
|
instance.canonical.currentProps = newProps;
|
|
return updatePayload;
|
|
}
|
|
|
|
export function resetAfterCommit(containerInfo: Container): void {
|
|
// Noop
|
|
}
|
|
|
|
export function shouldDeprioritizeSubtree(type: string, props: Props): boolean {
|
|
return false;
|
|
}
|
|
|
|
export function shouldSetTextContent(type: string, props: Props): boolean {
|
|
// TODO (bvaughn) Revisit this decision.
|
|
// Always returning false simplifies the createInstance() implementation,
|
|
// But creates an additional child Fiber for raw text children.
|
|
// No additional native views are created though.
|
|
// It's not clear to me which is better so I'm deferring for now.
|
|
// More context @ github.com/facebook/react/pull/8560#discussion_r92111303
|
|
return false;
|
|
}
|
|
|
|
// The Fabric renderer is secondary to the existing React Native renderer.
|
|
export const isPrimaryRenderer = false;
|
|
|
|
export const scheduleTimeout = setTimeout;
|
|
export const cancelTimeout = clearTimeout;
|
|
export const noTimeout = -1;
|
|
|
|
// -------------------
|
|
// Persistence
|
|
// -------------------
|
|
|
|
export const supportsPersistence = true;
|
|
|
|
export function cloneInstance(
|
|
instance: Instance,
|
|
updatePayload: null | Object,
|
|
type: string,
|
|
oldProps: Props,
|
|
newProps: Props,
|
|
internalInstanceHandle: Object,
|
|
keepChildren: boolean,
|
|
recyclableInstance: null | Instance,
|
|
): Instance {
|
|
const node = instance.node;
|
|
let clone;
|
|
if (keepChildren) {
|
|
if (updatePayload !== null) {
|
|
clone = cloneNodeWithNewProps(node, updatePayload);
|
|
} else {
|
|
clone = cloneNode(node);
|
|
}
|
|
} else {
|
|
if (updatePayload !== null) {
|
|
clone = cloneNodeWithNewChildrenAndProps(node, updatePayload);
|
|
} else {
|
|
clone = cloneNodeWithNewChildren(node);
|
|
}
|
|
}
|
|
return {
|
|
node: clone,
|
|
canonical: instance.canonical,
|
|
};
|
|
}
|
|
|
|
export function cloneHiddenInstance(
|
|
instance: Instance,
|
|
type: string,
|
|
props: Props,
|
|
internalInstanceHandle: Object,
|
|
): Instance {
|
|
const viewConfig = instance.canonical.viewConfig;
|
|
const node = instance.node;
|
|
const updatePayload = create(
|
|
{style: {display: 'none'}},
|
|
viewConfig.validAttributes,
|
|
);
|
|
return {
|
|
node: cloneNodeWithNewProps(node, updatePayload),
|
|
canonical: instance.canonical,
|
|
};
|
|
}
|
|
|
|
export function cloneHiddenTextInstance(
|
|
instance: Instance,
|
|
text: string,
|
|
internalInstanceHandle: Object,
|
|
): TextInstance {
|
|
throw new Error('Not yet implemented.');
|
|
}
|
|
|
|
export function createContainerChildSet(container: Container): ChildSet {
|
|
return createChildNodeSet(container);
|
|
}
|
|
|
|
export function appendChildToContainerChildSet(
|
|
childSet: ChildSet,
|
|
child: Instance | TextInstance,
|
|
): void {
|
|
appendChildNodeToSet(childSet, child.node);
|
|
}
|
|
|
|
export function finalizeContainerChildren(
|
|
container: Container,
|
|
newChildren: ChildSet,
|
|
): void {
|
|
completeRoot(container, newChildren);
|
|
}
|
|
|
|
export function replaceContainerChildren(
|
|
container: Container,
|
|
newChildren: ChildSet,
|
|
): void {}
|
|
|
|
export function mountEventComponent(
|
|
eventComponentInstance: ReactEventComponentInstance,
|
|
) {
|
|
throw new Error('Not yet implemented.');
|
|
}
|
|
|
|
export function updateEventComponent(
|
|
eventComponentInstance: ReactEventComponentInstance,
|
|
) {
|
|
throw new Error('Not yet implemented.');
|
|
}
|
|
|
|
export function unmountEventComponent(
|
|
eventComponentInstance: ReactEventComponentInstance,
|
|
): void {
|
|
throw new Error('Not yet implemented.');
|
|
}
|
|
|
|
export function getEventTargetChildElement(
|
|
type: Symbol | number,
|
|
props: Props,
|
|
): null {
|
|
throw new Error('Not yet implemented.');
|
|
}
|
|
|
|
export function handleEventTarget(
|
|
type: Symbol | number,
|
|
props: Props,
|
|
rootContainerInstance: Container,
|
|
internalInstanceHandle: Object,
|
|
): boolean {
|
|
throw new Error('Not yet implemented.');
|
|
}
|
|
|
|
export function commitEventTarget(
|
|
type: Symbol | number,
|
|
props: Props,
|
|
instance: Instance,
|
|
parentInstance: Instance,
|
|
): void {
|
|
throw new Error('Not yet implemented.');
|
|
}
|