メインコンテンツへスキップ

高階コンポーネント

Reactにおける一般的なパターンとして高階コンポーネントパターンがあり、Flowにおいて高階コンポーネントに対して効果的な型を提供することが重要です。高階コンポーネントについてまだご存知ない場合は、先にReactの高階コンポーネントに関するドキュメントをお読みください。

高階コンポーネントにアノテーションを付けるには、React.AbstractComponent型を使用できます。

単純なHOC

最も単純なHOCから始めましょう。

1import * as React from 'react';2
3function trivialHOC<Config: {...}>(4  Component: React.AbstractComponent<Config>5): React.AbstractComponent<Config> {6  return Component;7}

これは、HOCの一般的なテンプレートです。実行時には、このHOCは何もしません。より複雑な例を見てみましょう。

プロップスの注入

高階コンポーネントの一般的なユースケースは、プロップスの注入です。HOCは自動的にプロップを設定し、そのプロップを必要としなくなったコンポーネントを返します。例えば、ナビゲーションプロップを考えてみましょう。どのように型付けするのでしょうか?

設定からプロップを削除するには、プロップを含むコンポーネントを受け取り、そのプロップを含まないコンポーネントを返すことができます。これらの型は、オブジェクト型のスプレッドを使用して構築するのが最適です。

1import * as React from 'react';2
3type InjectedProps = {foo: number}4
5function injectProp<Config>(6  Component: React.AbstractComponent<{...Config, ...InjectedProps}>7): React.AbstractComponent<Config> {8  return function WrapperComponent(9    props: Config,10  ) {11    return <Component {...props} foo={42} />;12  };13}14
15function MyComponent(props: {16  a: number,17  b: number,18  ...InjectedProps,19}): React.Node {}20
21const MyEnhancedComponent = injectProp(MyComponent);22
23// We don't need to pass in `foo` even though `MyComponent` requires it:24<MyEnhancedComponent a={1} b={2} />; // OK25
26// We still require `a` and `b`:27<MyEnhancedComponent a={1} />; // ERROR
27:2-27:20: Cannot create `MyEnhancedComponent` element because property `b` is missing in props [1] but exists in object type [2]. [prop-missing]

コンポーネントのインスタンス型の保持

関数コンポーネントのインスタンス型はvoidであることを思い出してください。上記の例では、コンポーネントを関数でラップしているので、返されたコンポーネントのインスタンス型はvoidになります。

1import * as React from 'react';2
3type InjectedProps = {foo: number}4
5function injectProp<Config>(6  Component: React.AbstractComponent<{...Config, ...InjectedProps}>7): React.AbstractComponent<Config> {8  return function WrapperComponent(9    props: Config,10  ) {11    return <Component {...props} foo={42} />;12  };13}14
15// A class component in this example16class MyComponent extends React.Component<{17  a: number,18  b: number,19  ...InjectedProps,20}> {}21
22const MyEnhancedComponent = injectProp(MyComponent);23
24// If we create a ref object for the component, it will never be assigned25// an instance of MyComponent!26const ref = React.createRef<MyComponent>();27
28// Error, mixed is incompatible with MyComponent.29<MyEnhancedComponent ref={ref} a={1} b={2} />;
29:27-29:29: Cannot create `MyEnhancedComponent` element because in property `ref`: [incompatible-type] Either a call signature declaring the expected parameter / return type is missing in `React.RefObject` [1] but exists in function type [2]. Or `React.RefObject` [1] is incompatible with number [3].

このエラーメッセージが表示されるのは、React.AbstractComponent<Config>Instance型パラメータを設定していないため、自動的にmixedに設定されるためです。コンポーネントのインスタンス型を保持したい場合は、React.forwardRefを使用できます。

1import * as React from 'react';2
3type InjectedProps = {foo: number}4
5function injectAndPreserveInstance<Config, Instance>(6  Component: React.AbstractComponent<{...Config, ...InjectedProps}, Instance>7): React.AbstractComponent<Config, Instance> {8  return React.forwardRef<Config, Instance>((props, ref) =>9      <Component ref={ref} foo={3} {...props} />10  );11}12
13class MyComponent extends React.Component<{14  a: number,15  b: number,16  ...InjectedProps,17}> {}18
19const MyEnhancedComponent = injectAndPreserveInstance(MyComponent);20
21const ref = React.createRef<MyComponent>();22
23// All good! The ref is forwarded.24<MyEnhancedComponent ref={ref} a={1} b={2} />;

ラップされたコンポーネントのエクスポート

ラップされたコンポーネントをエクスポートしようとすると、アノテーションがないというエラーが発生する可能性があります。

1import * as React from 'react';2
3function trivialHOC<Config: {...}>(4  Component: React.AbstractComponent<Config>,5): React.AbstractComponent<Config> {6  return Component;7}8
9type Props = $ReadOnly<{bar: number, foo?: number}>;10
11function MyComponent({bar, foo = 3}: Props): React.Node {}12
13export const MyEnhancedComponent = trivialHOC(MyComponent); // ERROR
13:36-13:58: Cannot build a typed interface for this module. You should annotate the exports of this module with types. Cannot determine the type of this call expression. Please provide an annotation, e.g., by adding a type cast around this expression. [signature-verification-failure]

React.AbstractComponentを使用して、エクスポートされたコンポーネントにアノテーションを追加できます。

1import * as React from 'react';2
3function trivialHOC<Config: {...}>(4  Component: React.AbstractComponent<Config>,5): React.AbstractComponent<Config> {6  return Component;7}8
9type Props = $ReadOnly<{bar: number, foo?: number}>;10
11function MyComponent({bar, foo = 3}: Props): React.Node {}12
13export const MyEnhancedComponent: React.AbstractComponent<Props> = trivialHOC(MyComponent); // OK