Files
react/packages/react-native-renderer/src/NativeMethodsMixin.js
T
James Ide 61f62246c8 [react-native] Use path-based imports instead of Haste for the RN renderer (#15604)
* [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).
2019-05-23 08:23:54 +01:00

343 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 invariant from 'shared/invariant';
// Modules provided by RN:
import {
TextInputState,
UIManager,
} from 'react-native/Libraries/ReactPrivate/ReactNativePrivateInterface';
import {create} from './ReactNativeAttributePayload';
import {
mountSafeCallback_NOT_REALLY_SAFE,
throwOnStylesProp,
warnForStyleProps,
} from './NativeMethodsMixinUtils';
import warningWithoutStack from 'shared/warningWithoutStack';
import {warnAboutDeprecatedSetNativeProps} from 'shared/ReactFeatureFlags';
export default function(
findNodeHandle: any => ?number,
findHostInstance: any => any,
) {
/**
* `NativeMethodsMixin` provides methods to access the underlying native
* component directly. This can be useful in cases when you want to focus
* a view or measure its on-screen dimensions, for example.
*
* The methods described here are available on most of the default components
* provided by React Native. Note, however, that they are *not* available on
* composite components that aren't directly backed by a native view. This will
* generally include most components that you define in your own app. For more
* information, see [Direct
* Manipulation](docs/direct-manipulation.html).
*
* Note the Flow $Exact<> syntax is required to support mixins.
* React createClass mixins can only be used with exact types.
*/
const NativeMethodsMixin: $Exact<NativeMethodsMixinType> = {
/**
* Determines the location on screen, width, and height of the given view and
* returns the values via an async callback. If successful, the callback will
* be called with the following arguments:
*
* - x
* - y
* - width
* - height
* - pageX
* - pageY
*
* Note that these measurements are not available until after the rendering
* has been completed in native. If you need the measurements as soon as
* possible, consider using the [`onLayout`
* prop](docs/view.html#onlayout) instead.
*/
measure: function(callback: MeasureOnSuccessCallback) {
let maybeInstance;
// Fiber errors if findNodeHandle is called for an umounted component.
// Tests using ReactTestRenderer will trigger this case indirectly.
// Mimicking stack behavior, we should silently ignore this case.
// TODO Fix ReactTestRenderer so we can remove this try/catch.
try {
maybeInstance = findHostInstance(this);
} catch (error) {}
// If there is no host component beneath this we should fail silently.
// This is not an error; it could mean a class component rendered null.
if (maybeInstance == null) {
return;
}
if (maybeInstance.canonical) {
// We can't call FabricUIManager here because it won't be loaded in paper
// at initialization time. See https://github.com/facebook/react/pull/15490
// for more info.
nativeFabricUIManager.measure(
maybeInstance.node,
mountSafeCallback_NOT_REALLY_SAFE(this, callback),
);
} else {
UIManager.measure(
findNodeHandle(this),
mountSafeCallback_NOT_REALLY_SAFE(this, callback),
);
}
},
/**
* Determines the location of the given view in the window and returns the
* values via an async callback. If the React root view is embedded in
* another native view, this will give you the absolute coordinates. If
* successful, the callback will be called with the following
* arguments:
*
* - x
* - y
* - width
* - height
*
* Note that these measurements are not available until after the rendering
* has been completed in native.
*/
measureInWindow: function(callback: MeasureInWindowOnSuccessCallback) {
let maybeInstance;
// Fiber errors if findNodeHandle is called for an umounted component.
// Tests using ReactTestRenderer will trigger this case indirectly.
// Mimicking stack behavior, we should silently ignore this case.
// TODO Fix ReactTestRenderer so we can remove this try/catch.
try {
maybeInstance = findHostInstance(this);
} catch (error) {}
// If there is no host component beneath this we should fail silently.
// This is not an error; it could mean a class component rendered null.
if (maybeInstance == null) {
return;
}
if (maybeInstance.canonical) {
// We can't call FabricUIManager here because it won't be loaded in paper
// at initialization time. See https://github.com/facebook/react/pull/15490
// for more info.
nativeFabricUIManager.measureInWindow(
maybeInstance.node,
mountSafeCallback_NOT_REALLY_SAFE(this, callback),
);
} else {
UIManager.measureInWindow(
findNodeHandle(this),
mountSafeCallback_NOT_REALLY_SAFE(this, callback),
);
}
},
/**
* Like [`measure()`](#measure), but measures the view relative an ancestor,
* specified as `relativeToNativeNode`. This means that the returned x, y
* are relative to the origin x, y of the ancestor view.
*
* As always, to obtain a native node handle for a component, you can use
* `findNodeHandle(component)`.
*/
measureLayout: function(
relativeToNativeNode: number | Object,
onSuccess: MeasureLayoutOnSuccessCallback,
onFail: () => void /* currently unused */,
) {
let maybeInstance;
// Fiber errors if findNodeHandle is called for an umounted component.
// Tests using ReactTestRenderer will trigger this case indirectly.
// Mimicking stack behavior, we should silently ignore this case.
// TODO Fix ReactTestRenderer so we can remove this try/catch.
try {
maybeInstance = findHostInstance(this);
} catch (error) {}
// If there is no host component beneath this we should fail silently.
// This is not an error; it could mean a class component rendered null.
if (maybeInstance == null) {
return;
}
if (maybeInstance.canonical) {
warningWithoutStack(
false,
'Warning: measureLayout on components using NativeMethodsMixin ' +
'or ReactNative.NativeComponent is not currently supported in Fabric. ' +
'measureLayout must be called on a native ref. Consider using forwardRef.',
);
return;
} else {
let relativeNode;
if (typeof relativeToNativeNode === 'number') {
// Already a node handle
relativeNode = relativeToNativeNode;
} else if (relativeToNativeNode._nativeTag) {
relativeNode = relativeToNativeNode._nativeTag;
}
if (relativeNode == null) {
warningWithoutStack(
false,
'Warning: ref.measureLayout must be called with a node handle or a ref to a native component.',
);
return;
}
UIManager.measureLayout(
findNodeHandle(this),
relativeNode,
mountSafeCallback_NOT_REALLY_SAFE(this, onFail),
mountSafeCallback_NOT_REALLY_SAFE(this, onSuccess),
);
}
},
/**
* This function sends props straight to native. They will not participate in
* future diff process - this means that if you do not include them in the
* next render, they will remain active (see [Direct
* Manipulation](docs/direct-manipulation.html)).
*/
setNativeProps: function(nativeProps: Object) {
// Class components don't have viewConfig -> validateAttributes.
// Nor does it make sense to set native props on a non-native component.
// Instead, find the nearest host component and set props on it.
// Use findNodeHandle() rather than findNodeHandle() because
// We want the instance/wrapper (not the native tag).
let maybeInstance;
// Fiber errors if findNodeHandle is called for an umounted component.
// Tests using ReactTestRenderer will trigger this case indirectly.
// Mimicking stack behavior, we should silently ignore this case.
// TODO Fix ReactTestRenderer so we can remove this try/catch.
try {
maybeInstance = findHostInstance(this);
} catch (error) {}
// If there is no host component beneath this we should fail silently.
// This is not an error; it could mean a class component rendered null.
if (maybeInstance == null) {
return;
}
if (maybeInstance.canonical) {
warningWithoutStack(
false,
'Warning: setNativeProps is not currently supported in Fabric',
);
return;
}
if (__DEV__) {
if (warnAboutDeprecatedSetNativeProps) {
warningWithoutStack(
false,
'Warning: Calling ref.setNativeProps(nativeProps) ' +
'is deprecated and will be removed in a future release. ' +
'Use the setNativeProps export from the react-native package instead.' +
"\n\timport {setNativeProps} from 'react-native';\n\tsetNativeProps(ref, nativeProps);\n",
);
}
}
const nativeTag =
maybeInstance._nativeTag || maybeInstance.canonical._nativeTag;
const viewConfig: ReactNativeBaseComponentViewConfig<> =
maybeInstance.viewConfig || maybeInstance.canonical.viewConfig;
if (__DEV__) {
warnForStyleProps(nativeProps, viewConfig.validAttributes);
}
const updatePayload = create(nativeProps, viewConfig.validAttributes);
// Avoid the overhead of bridge calls if there's no update.
// This is an expensive no-op for Android, and causes an unnecessary
// view invalidation for certain components (eg RCTTextInput) on iOS.
if (updatePayload != null) {
UIManager.updateView(
nativeTag,
viewConfig.uiViewClassName,
updatePayload,
);
}
},
/**
* Requests focus for the given input or view. The exact behavior triggered
* will depend on the platform and type of view.
*/
focus: function() {
TextInputState.focusTextInput(findNodeHandle(this));
},
/**
* Removes focus from an input or view. This is the opposite of `focus()`.
*/
blur: function() {
TextInputState.blurTextInput(findNodeHandle(this));
},
};
if (__DEV__) {
// hide this from Flow since we can't define these properties outside of
// __DEV__ without actually implementing them (setting them to undefined
// isn't allowed by ReactClass)
const NativeMethodsMixin_DEV = (NativeMethodsMixin: any);
invariant(
!NativeMethodsMixin_DEV.componentWillMount &&
!NativeMethodsMixin_DEV.componentWillReceiveProps &&
!NativeMethodsMixin_DEV.UNSAFE_componentWillMount &&
!NativeMethodsMixin_DEV.UNSAFE_componentWillReceiveProps,
'Do not override existing functions.',
);
// TODO (bvaughn) Remove cWM and cWRP in a future version of React Native,
// Once these lifecycles have been remove from the reconciler.
NativeMethodsMixin_DEV.componentWillMount = function() {
throwOnStylesProp(this, this.props);
};
NativeMethodsMixin_DEV.componentWillReceiveProps = function(newProps) {
throwOnStylesProp(this, newProps);
};
NativeMethodsMixin_DEV.UNSAFE_componentWillMount = function() {
throwOnStylesProp(this, this.props);
};
NativeMethodsMixin_DEV.UNSAFE_componentWillReceiveProps = function(
newProps,
) {
throwOnStylesProp(this, newProps);
};
// React may warn about cWM/cWRP/cWU methods being deprecated.
// Add a flag to suppress these warnings for this special case.
// TODO (bvaughn) Remove this flag once the above methods have been removed.
NativeMethodsMixin_DEV.componentWillMount.__suppressDeprecationWarning = true;
NativeMethodsMixin_DEV.componentWillReceiveProps.__suppressDeprecationWarning = true;
}
return NativeMethodsMixin;
}