Spec-Zone.ru › Flow

Компоненты высшего порядка

Популярный шаблон в React — это шаблон компонентов высшего порядка, поэтому важно, чтобы мы могли предоставлять эффективные типы для компонентов высшего порядка в Flow. Если вы ещё не знаете, что такое компонент высшего порядка, обязательно прочитайте документацию React по компонентам высшего порядка, прежде чем продолжить.

В версии 0.89.0 мы представили React.AbstractComponent, что предоставляет вам большую выразительность при написании HOC и определении библиотек.

Давайте посмотрим, как вы можете типизировать некоторые примеры HOC.

Тривиальный HOC

Давайте начнём с самого простого HOC:

//@flow
import * as React from 'react';

function trivialHOC<Config: {}>(
  Component: React.AbstractComponent<Config>
): React.AbstractComponent<Config> {
  return Component;
}

Это базовая структура, которую могут иметь ваши HOC. Во время выполнения этот HOC ничего не делает. Давайте посмотрим на некоторые более сложные примеры.

Вставка свойств

Распространённое применение компонентов высшего порядка — это вставка свойства. HOC автоматически устанавливает свойство и возвращает компонент, который больше не требует этого свойства. Например, рассмотрите свойство навигации или, в случае react-redux store свойство. Как это типизировать?

Чтобы удалить свойство из конфигурации, мы можем взять компонент, который включает это свойство, и вернуть компонент, который его не использует. Лучше всего строить эти типы, используя оператор распространения объекта.

//@flow
import * as React from 'react';

type InjectedProps = {| foo: number |}

function injectProp<Config>(
  Component: React.AbstractComponent<{| ...Config, ...InjectedProps |}>
): React.AbstractComponent<Config> {
  return function WrapperComponent(
    props: Config,
  ) {
    return <Component {...props} foo={42} />;
  };
}

class MyComponent extends React.Component<{|
  a: number,
  b: number,
  ...InjectedProps,
|}> {}

const MyEnhancedComponent = injectProp(MyComponent);

// We don't need to pass in `foo` even though `MyComponent` requires it.
<MyEnhancedComponent a={1} b={2} />;

Сохранение типа экземпляра компонента

Вспомните, что тип экземпляра функционального компонента — void. Наш пример выше оборачивает компонент в функцию, поэтому возвращаемый компонент имеет тип экземпляра void.

//@flow
import * as React from 'react';

type InjectedProps = {| foo: number |}

function injectProp<Config>(
  Component: React.AbstractComponent<{| ...Config, ...InjectedProps |}>
): React.AbstractComponent<Config> {
  return function WrapperComponent(
    props: Config,
  ) {
    return <Component {...props} foo={42} />;
  };
}

class MyComponent extends React.Component<{|
  a: number,
  b: number,
  ...InjectedProps,
|}> {}

const MyEnhancedComponent = injectProp(MyComponent);

// If we create a ref object for the component, it will never be assigned
// an instance of MyComponent!
const ref = React.createRef<MyComponent>();

// Error, mixed is incompatible with MyComponent.
<MyEnhancedComponent ref={ref} a={1} b={2} />;

Мы получаем это сообщение об ошибке, потому что React.AbstractComponent<Config> не задаёт параметр типа Instance, поэтому он автоматически устанавливается в mixed. Если мы хотим сохранить тип экземпляра компонента, мы можем использовать React.forwardRef:

//@flow
import * as React from 'react';

type InjectedProps = {| foo: number |}

function injectAndPreserveInstance<Config, Instance>(
  Component: React.AbstractComponent<{| ...Config, ...InjectedProps |}, Instance>
): React.AbstractComponent<Config, Instance> {
  return React.forwardRef<Config, Instance>((props, ref) =>
      <Component ref={ref} foo={3} {...props} />
  );
}

class MyComponent extends React.Component<{
  a: number,
  b: number,
  ...InjectedProps,
}> {}

const MyEnhancedComponent = injectAndPreserveInstance(MyComponent);

const ref = React.createRef<MyComponent>();

// All good! The ref is forwarded.
<MyEnhancedComponent ref={ref} a={1} b={2} />;

Экспорт обернутых компонентов

Если вы попытаетесь экспортировать обернутый компонент, скорее всего, столкнётесь с ошибкой отсутствия аннотации:

//@flow
import * as React from 'react';

function trivialHOC<Config: {}>(
  Component: React.AbstractComponent<Config>,
): React.AbstractComponent<Config> {
  return Component;
}

type DefaultProps = {| foo: number |};
type Props = {...DefaultProps, bar: number};

class MyComponent extends React.Component<Props> {
  static defaultProps: DefaultProps = {foo: 3};
}

// Error, missing annotation for Config.
const MyEnhancedComponent = trivialHOC(MyComponent);

module.exports = MyEnhancedComponent;

Если у вашего компонента нет defaultProps, вы можете использовать Props в качестве аргумента типа для Config.

Если у вашего компонента есть defaultProps, вы не хотите просто добавить Props в качестве аргумента типа для trivialHOC, потому что это удалит информацию о вашем компоненте, которую имеет Flow.

Вот где React.Config<Props, DefaultProps> очень пригодится! Мы можем использовать тип для Props и DefaultProps, чтобы рассчитать тип Config для нашего компонента.

//@flow
import * as React from 'react';

function trivialHOC<Config: {}>(
  Component: React.AbstractComponent<Config>,
): React.AbstractComponent<Config> {
  return Component;
}

type DefaultProps = {| foo: number |};
type Props = {...DefaultProps, bar: number};

class MyComponent extends React.Component<Props> {
  static defaultProps: DefaultProps = {foo: 3};
}

const MyEnhancedComponent = trivialHOC<React.Config<Props, DefaultProps>>(MyComponent);

// Ok!
module.exports = MyEnhancedComponent;

© 2013–present Facebook Inc.
Licensed under the MIT License.
https://flow.org/en/docs/react/hoc

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API