Skip to content

Commit 69c38e5

Browse files
elicwhitefacebook-github-bot
authored andcommitted
Introduce flow type to differentiate between HostComponent, NativeMethodsMixin, and NativeComponent
Summary: In React Native there are three types of "Native" components. ``` createReactClass with NativeMethodsMixin ``` ``` class MyComponent extends ReactNative.NativeComponent ``` ``` requireNativeComponent('RCTView') ``` The implementation for how to handle all three of these exists in the React Native Renderer. Refs attached to components created via these methods provide a set of functions such as ``` .measure .measureInWindow .measureLayout .setNativeProps ``` These methods have been used for our core components in the repo to provide a consistent API. Many of the APIs in React Native require a `reactTag` to a host component. This is acquired by calling `findNodeHandle` with any component. `findNodeHandle` works with the first two approaches. For a lot of our new Fabric APIs, we will require passing a ref to a HostComponent directly instead of relying on `findNodeHandle` to tunnel through the component tree as that behavior isn't safe with React concurrent mode. The goal of this change is to enable us to differentiate between components created with `requireNativeComponent` and the other types. This will be needed to be able to safely type the new APIs. For existing components that should support being a host component but need to use some JS behavior in a wrapper, they should use `forwardRef`. The majority of React Native's core components were migrated to use `forwardRef` last year. Components that can't use forwardRef will need to have a method like `getNativeRef()` to get access to the underlying host component ref. Note, we will need follow up changes as well as changes to the React Renderer in the React repo to fully utilize this new type. Changelog: [Internal] Flow type to differentiate between HostComponent and NativeMethodsMixin and NativeComponent Reviewed By: jbrown215 Differential Revision: D17551089 fbshipit-source-id: 7a30b4bb4323156c0b2465ca41fcd05f4315becf
1 parent 94e8ccf commit 69c38e5

File tree

13 files changed

+109
-69
lines changed

13 files changed

+109
-69
lines changed

Libraries/Components/RefreshControl/__mocks__/RefreshControlMock.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,18 @@ const React = require('react');
1414

1515
const requireNativeComponent = require('../../../ReactNative/requireNativeComponent');
1616

17-
const RCTRefreshControl = requireNativeComponent('RCTRefreshControl');
17+
import type {HostComponent} from '../../../Renderer/shims/ReactNativeTypes';
18+
19+
const RCTRefreshControl: HostComponent<mixed> = requireNativeComponent<mixed>(
20+
'RCTRefreshControl',
21+
);
1822

1923
class RefreshControlMock extends React.Component<{}> {
2024
static latestRef: ?RefreshControlMock;
2125
componentDidMount() {
2226
RefreshControlMock.latestRef = this;
2327
}
24-
render(): React.Element<string> {
28+
render(): React.Element<typeof RCTRefreshControl> {
2529
return <RCTRefreshControl />;
2630
}
2731
}

Libraries/Components/ScrollResponder.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import type {PressEvent, ScrollEvent} from '../Types/CoreEventTypes';
2828
import type {Props as ScrollViewProps} from './ScrollView/ScrollView';
2929
import type {KeyboardEvent} from './Keyboard/Keyboard';
3030
import type EmitterSubscription from '../vendor/emitter/EmitterSubscription';
31+
import type {HostComponent} from '../Renderer/shims/ReactNativeTypes';
3132

3233
/**
3334
* Mixin that can be integrated in order to handle scrolling that plays well
@@ -543,6 +544,7 @@ const ScrollResponderMixin = {
543544
scrollResponderScrollNativeHandleToKeyboard: function<T>(
544545
nodeHandle:
545546
| number
547+
| React.ElementRef<HostComponent<T>>
546548
| React.ElementRef<Class<ReactNative.NativeComponent<T>>>,
547549
additionalOffset?: number,
548550
preventNegativeScrollOffset?: boolean,

Libraries/Components/ScrollView/__mocks__/ScrollViewMock.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,16 @@ const View = require('../../View/View');
1717

1818
const requireNativeComponent = require('../../../ReactNative/requireNativeComponent');
1919

20-
const RCTScrollView = requireNativeComponent('RCTScrollView');
20+
import type {HostComponent} from '../../../Renderer/shims/ReactNativeTypes';
21+
22+
const RCTScrollView: HostComponent<mixed> = requireNativeComponent<mixed>(
23+
'RCTScrollView',
24+
);
2125

2226
const ScrollViewComponent: $FlowFixMe = jest.genMockFromModule('../ScrollView');
2327

2428
class ScrollViewMock extends ScrollViewComponent {
25-
render(): React.Element<string> {
29+
render(): React.Element<typeof RCTScrollView> {
2630
return (
2731
<RCTScrollView {...this.props}>
2832
{this.props.refreshControl}

Libraries/Components/TextInput/TextInput.js

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import type {ColorValue} from '../../StyleSheet/StyleSheetTypes';
3232
import type {ViewProps} from '../View/ViewPropTypes';
3333
import type {SyntheticEvent, ScrollEvent} from '../../Types/CoreEventTypes';
3434
import type {PressEvent} from '../../Types/CoreEventTypes';
35+
import type {HostComponent} from '../../Renderer/shims/ReactNativeTypes';
3536

3637
let AndroidTextInput;
3738
let RCTMultilineTextInputView;
@@ -904,9 +905,7 @@ const TextInput = createReactClass({
904905
this._inputRef = ref;
905906
},
906907

907-
getNativeRef: function(): ?React.ElementRef<
908-
Class<ReactNative.NativeComponent<mixed>>,
909-
> {
908+
getNativeRef: function(): ?React.ElementRef<HostComponent<mixed>> {
910909
return this._inputRef;
911910
},
912911

@@ -1230,9 +1229,7 @@ const TextInput = createReactClass({
12301229
class InternalTextInputType extends ReactNative.NativeComponent<Props> {
12311230
clear() {}
12321231

1233-
getNativeRef(): ?React.ElementRef<
1234-
Class<ReactNative.NativeComponent<mixed>>,
1235-
> {}
1232+
getNativeRef(): ?React.ElementRef<HostComponent<mixed>> {}
12361233

12371234
// $FlowFixMe
12381235
isFocused(): boolean {}

Libraries/Components/View/ViewNativeComponent.js

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,15 @@
1111
'use strict';
1212

1313
const Platform = require('../../Utilities/Platform');
14-
const ReactNative = require('../../Renderer/shims/ReactNative');
1514
const ReactNativeViewViewConfigAndroid = require('./ReactNativeViewViewConfigAndroid');
1615

1716
const registerGeneratedViewConfig = require('../../Utilities/registerGeneratedViewConfig');
1817
const requireNativeComponent = require('../../ReactNative/requireNativeComponent');
1918

2019
import type {ViewProps} from './ViewPropTypes';
20+
import type {HostComponent} from '../../Renderer/shims/ReactNativeTypes';
2121

22-
export type ViewNativeComponentType = Class<
23-
ReactNative.NativeComponent<ViewProps>,
24-
>;
22+
export type ViewNativeComponentType = HostComponent<ViewProps>;
2523

2624
let NativeViewComponent;
2725
let viewConfig:

Libraries/Image/Image.android.js

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -207,15 +207,15 @@ async function queryCache(
207207
return await ImageLoader.queryCache(urls);
208208
}
209209

210-
declare class ImageComponentType extends ReactNative.NativeComponent<ImagePropsType> {
211-
static getSize: typeof getSize;
212-
static getSizeWithHeaders: typeof getSizeWithHeaders;
213-
static prefetch: typeof prefetch;
214-
static abortPrefetch: typeof abortPrefetch;
215-
static queryCache: typeof queryCache;
216-
static resolveAssetSource: typeof resolveAssetSource;
217-
static propTypes: typeof ImageProps;
218-
}
210+
type ImageComponentStatics = $ReadOnly<{|
211+
getSize: typeof getSize,
212+
getSizeWithHeaders: typeof getSizeWithHeaders,
213+
prefetch: typeof prefetch,
214+
abortPrefetch: typeof abortPrefetch,
215+
queryCache: typeof queryCache,
216+
resolveAssetSource: typeof resolveAssetSource,
217+
propTypes: typeof ImageProps,
218+
|}>;
219219

220220
/**
221221
* A React component for displaying different types of images,
@@ -224,10 +224,7 @@ declare class ImageComponentType extends ReactNative.NativeComponent<ImagePropsT
224224
*
225225
* See https://facebook.github.io/react-native/docs/image.html
226226
*/
227-
let Image = (
228-
props: ImagePropsType,
229-
forwardedRef: ?React.Ref<'RCTTextInlineImage' | 'ImageViewNativeComponent'>,
230-
) => {
227+
let Image = (props: ImagePropsType, forwardedRef) => {
231228
let source = resolveAssetSource(props.source);
232229
const defaultSource = resolveAssetSource(props.defaultSource);
233230
const loadingIndicatorSource = resolveAssetSource(
@@ -303,7 +300,12 @@ let Image = (
303300
);
304301
};
305302

306-
Image = React.forwardRef(Image);
303+
Image = React.forwardRef<
304+
ImagePropsType,
305+
| React.ElementRef<typeof TextInlineImageNativeComponent>
306+
| React.ElementRef<typeof ImageViewNativeComponent>,
307+
>(Image);
308+
307309
Image.displayName = 'Image';
308310

309311
/**
@@ -379,7 +381,9 @@ const styles = StyleSheet.create({
379381
},
380382
});
381383

382-
/* $FlowFixMe(>=0.89.0 site=react_native_android_fb) This comment suppresses an
383-
* error found when Flow v0.89 was deployed. To see the error, delete this
384-
* comment and run Flow. */
385-
module.exports = (Image: Class<ImageComponentType>);
384+
module.exports = ((Image: any): React.AbstractComponent<
385+
ImagePropsType,
386+
| React.ElementRef<typeof TextInlineImageNativeComponent>
387+
| React.ElementRef<typeof ImageViewNativeComponent>,
388+
> &
389+
ImageComponentStatics);

Libraries/Image/Image.ios.js

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,15 @@ const flattenStyle = require('../StyleSheet/flattenStyle');
1919
const requireNativeComponent = require('../ReactNative/requireNativeComponent');
2020
const resolveAssetSource = require('./resolveAssetSource');
2121

22-
const ImageViewManager = NativeModules.ImageViewManager;
23-
24-
const RCTImageView = requireNativeComponent('RCTImageView');
25-
2622
import type {ImageProps as ImagePropsType} from './ImageProps';
27-
23+
import type {HostComponent} from '../Renderer/shims/ReactNativeTypes';
2824
import type {ImageStyleProp} from '../StyleSheet/StyleSheet';
2925

26+
const ImageViewManager = NativeModules.ImageViewManager;
27+
const RCTImageView: HostComponent<mixed> = requireNativeComponent(
28+
'RCTImageView',
29+
);
30+
3031
function getSize(
3132
uri: string,
3233
success: (width: number, height: number) => void,
@@ -70,14 +71,14 @@ async function queryCache(
7071
return await ImageViewManager.queryCache(urls);
7172
}
7273

73-
declare class ImageComponentType extends ReactNative.NativeComponent<ImagePropsType> {
74-
static getSize: typeof getSize;
75-
static getSizeWithHeaders: typeof getSizeWithHeaders;
76-
static prefetch: typeof prefetch;
77-
static queryCache: typeof queryCache;
78-
static resolveAssetSource: typeof resolveAssetSource;
79-
static propTypes: typeof DeprecatedImagePropType;
80-
}
74+
type ImageComponentStatics = $ReadOnly<{|
75+
getSize: typeof getSize,
76+
getSizeWithHeaders: typeof getSizeWithHeaders,
77+
prefetch: typeof prefetch,
78+
queryCache: typeof queryCache,
79+
resolveAssetSource: typeof resolveAssetSource,
80+
propTypes: typeof DeprecatedImagePropType,
81+
|}>;
8182

8283
/**
8384
* A React component for displaying different types of images,
@@ -86,10 +87,7 @@ declare class ImageComponentType extends ReactNative.NativeComponent<ImagePropsT
8687
*
8788
* See https://facebook.github.io/react-native/docs/image.html
8889
*/
89-
let Image = (
90-
props: ImagePropsType,
91-
forwardedRef: ?React.Ref<'RCTImageView'>,
92-
) => {
90+
let Image = (props: ImagePropsType, forwardedRef) => {
9391
const source = resolveAssetSource(props.source) || {
9492
uri: undefined,
9593
width: undefined,
@@ -140,7 +138,9 @@ let Image = (
140138
);
141139
};
142140

143-
Image = React.forwardRef(Image);
141+
Image = React.forwardRef<ImagePropsType, React.ElementRef<typeof RCTImageView>>(
142+
Image,
143+
);
144144
Image.displayName = 'Image';
145145

146146
/**
@@ -206,7 +206,8 @@ const styles = StyleSheet.create({
206206
},
207207
});
208208

209-
/* $FlowFixMe(>=0.89.0 site=react_native_ios_fb) This comment suppresses an
210-
* error found when Flow v0.89 was deployed. To see the error, delete this
211-
* comment and run Flow. */
212-
module.exports = (Image: Class<ImageComponentType>);
209+
module.exports = ((Image: any): React.AbstractComponent<
210+
ImagePropsType,
211+
React.ElementRef<typeof RCTImageView>,
212+
> &
213+
ImageComponentStatics);

Libraries/Image/ImageViewNativeComponent.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,17 @@
55
* LICENSE file in the root directory of this source tree.
66
*
77
* @format
8-
* @flow
8+
* @flow strict-local
99
*/
1010

1111
'use strict';
1212

1313
const requireNativeComponent = require('../ReactNative/requireNativeComponent');
1414

15-
const ImageViewNativeComponent: string = requireNativeComponent('RCTImageView');
15+
import type {HostComponent} from '../Renderer/shims/ReactNativeTypes';
16+
17+
const ImageViewNativeComponent: HostComponent<mixed> = requireNativeComponent<mixed>(
18+
'RCTImageView',
19+
);
1620

1721
module.exports = ImageViewNativeComponent;

Libraries/Image/TextInlineImageNativeComponent.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,16 @@
55
* LICENSE file in the root directory of this source tree.
66
*
77
* @format
8-
* @flow
8+
* @flow strict-local
99
*/
1010

1111
'use strict';
1212

1313
const requireNativeComponent = require('../ReactNative/requireNativeComponent');
14+
import type {HostComponent} from '../Renderer/shims/ReactNativeTypes';
1415

15-
const TextInlineImage: string = requireNativeComponent('RCTTextInlineImage');
16+
const TextInlineImage: HostComponent<mixed> = requireNativeComponent<mixed>(
17+
'RCTTextInlineImage',
18+
);
1619

1720
module.exports = TextInlineImage;

Libraries/ReactNative/requireNativeComponent.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
66
*
7-
* @flow strict-local
7+
* @flow
88
* @format
99
*/
1010

@@ -13,6 +13,8 @@
1313
const createReactNativeComponentClass = require('../Renderer/shims/createReactNativeComponentClass');
1414
const getNativeComponentAttributes = require('./getNativeComponentAttributes');
1515

16+
import type {HostComponent} from '../Renderer/shims/ReactNativeTypes';
17+
1618
/**
1719
* Creates values that can be used like React components which represent native
1820
* view managers. You should create JavaScript modules that wrap these values so
@@ -21,9 +23,10 @@ const getNativeComponentAttributes = require('./getNativeComponentAttributes');
2123
* const View = requireNativeComponent('RCTView');
2224
*
2325
*/
24-
const requireNativeComponent = (uiViewClassName: string): string =>
25-
createReactNativeComponentClass(uiViewClassName, () =>
26+
27+
const requireNativeComponent = <T>(uiViewClassName: string): HostComponent<T> =>
28+
((createReactNativeComponentClass(uiViewClassName, () =>
2629
getNativeComponentAttributes(uiViewClassName),
27-
);
30+
): any): HostComponent<T>);
2831

2932
module.exports = requireNativeComponent;

0 commit comments

Comments
 (0)