高階元件
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 完全沒有作用。讓我們看看一些更複雜的範例。
注入道具
較高階元件的常見使用案例是注入 prop。HOC 會自動設定 prop,並回傳不再需要該 prop 的元件。例如,考量導覽 prop。要如何輸入這個 prop?
若要從組態中移除 prop,我們可以採用包含 prop 的元件,並回傳不包含 prop 的元件。建議使用物件類型擴充來建構這些類型。
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