高阶组件
现代 React 代码不鼓励使用高阶组件,并且不会针对 组件语法 进行更新。考虑使用钩子来完成你的任务。
¥Higher-order components are discouraged in modern React code and will not be updated for Component Syntax. Consider using a hook to accomplish your task instead.
React 中流行的模式是 高阶组件模式,因此我们能为 Flow 中的高阶组件提供有效的类型非常重要。如果你还不知道什么是高阶组件,请确保在继续之前阅读 有关高阶组件的 React 文档。
¥A popular pattern in React is the higher-order component pattern, so it's important that we can provide effective types for higher-order components in Flow. If you don't already know what a higher-order component is then make sure to read the React documentation on higher-order components before continuing.
你可以使用 React.AbstractComponent
类型来注释你的高阶组件。
¥You can make use of the React.AbstractComponent
type to annotate your higher order components.
平凡的 HOC
¥The Trivial HOC
让我们从最简单的 HOC 开始:
¥Let's start with the simplest 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 根本不做任何事情。让我们看一些更复杂的例子。
¥This is a basic template for what your HOCs might look like. At runtime, this HOC doesn't do anything at all. Let's take a look at some more complex examples.
注入属性
¥Injecting Props
高阶组件的一个常见用例是注入 prop。HOC 自动设置一个 prop 并返回一个不再需要该 prop 的组件。例如,考虑一个导航属性。如何输入这个?
¥A common use case for higher-order components is to inject a prop. The HOC automatically sets a prop and returns a component which no longer requires that prop. For example, consider a navigation prop. How would one type this?
要从配置中删除某个 prop,我们可以获取包含该 prop 的组件并返回不包含该 prop 的组件。最好使用对象类型扩展来构造这些类型。
¥To remove a prop from the config, we can take a component that includes the prop and return a component that does not. It's best to construct these types using object type spread.
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
保留组件的实例类型
¥Preserving the Instance Type of a Component
回想一下,函数组件的实例类型是 void
。上面的示例将组件封装在函数中,因此返回的组件具有实例类型 void
。
¥Recall that the instance type of a function component is void
. Our example
above wraps a component in a function, so the returned component has the instance
type 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} />;
我们收到此错误消息是因为 React.AbstractComponent<Config>
没有设置 Instance
类型参数,因此它被自动设置为 mixed
。如果我们想保留组件的实例类型,我们可以使用 React.forwardRef
:
¥We get this error message because React.AbstractComponent<Config>
doesn't set the Instance
type
parameter, so it is automatically set to mixed
. If we wanted to preserve the instance type
of the component, we can use 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} />;
导出封装组件
¥Exporting Wrapped Components
如果你尝试导出封装的组件,你很可能会遇到缺少注释的错误:
¥If you try to export a wrapped component, chances are that you'll run into a missing annotation error:
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
你可以使用 React.AbstractComponent
将注释添加到导出的组件:
¥You can add an annotation to your exported component using 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