Skip to main content

高阶组件

危险

现代 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.

你可以使用 组件类型 来注释你的高阶组件。

¥You can make use of the Component Types 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: component(...Config),5): component(...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: component(...{...$Exact<Config>, ...InjectedProps})7): component(...$Exact<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: component(...{...$Exact<Config>, ...InjectedProps})7): component(...$Exact<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} />;

我们收到此错误消息是因为组件类型未声明 ref prop,因此它被视为 React.RefSetter<void>。如果我们想保留组件的实例类型,我们可以使用 React.forwardRef

¥We get this error message because component type doesn't declare the ref prop, so it is treated as React.RefSetter<void>. 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: component(ref?: React.RefSetter<Instance>, ...{...$Exact<Config>, ...InjectedProps})7): component(ref?: React.RefSetter<Instance>, ...$Exact<Config>) {8  return React.forwardRef<$Exact<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: component(...Config),5): component(...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

你可以使用组件类型向导出的组件添加注释:

¥You can add an annotation to your exported component using component types:

1import * as React from 'react';2
3function trivialHOC<Config: {...}>(4  Component: component(...Config),5): component(...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: component(...Props) = trivialHOC(MyComponent); // OK