Режимы машинописного ввода: типы свойств компонента доступа

npm package @types/react позволяет нам использовать React внутри наших приложений TypeScript. Мы определяем компоненты как

type Props = {...}

type State = {...}

export default class MyComponent extends React.Component<Props, State> {
}

здесь мы должны объявлять типы для компонентного реквизита и состояния (в переменных типа).

После того, как мы объявили эти типы, TypeScript использует это для проверки использования нашего компонента (форма реквизита, переданного ему).

Я хочу создать контейнер вокруг такого компонента. Контейнер будет повторно использовать опоры компонента. Но для того, чтобы создать другой компонент с тем же реквизитом, я снова должен повторно использовать типы для реквизита. Или экспортируйте их из исходного файла компонента и импортируйте в контейнер:

// original file
export type Props = {...}

// container file
import MyComponent, { Props } from './original'

Но я уже импортирую MyComponent из этого файла. Этот компонент уже содержит информацию о реквизитах, которые он потребляет (благодаря переменным типа в React.Component).

Вопрос в том, как я могу получить доступ к этой информации из самого класса компонента без явного экспорта/импорта типа для реквизита?

Я хочу что-то вроде:

import MyComponent from './MyComponent'

type Props = MyComponent.Props // <= here access the component prop types

export default class MyContainer extends React.Component<Props, {}> {}

Ответ 1

2019: заметил, что все ответы выше довольно устарели, поэтому вот свежий.


Тип поиска

В новых версиях TS вы можете использовать типы поиска.

type ViewProps = View['props']

Несмотря на то, что это очень удобно, это будет работать только с компонентами класса.


React.ComponentProps

В React typedefs поставляется утилита для извлечения типа реквизита из любого компонента.

type ViewProps = React.ComponentProps<typeof View>

type InputProps = React.ComponentProps<'input'>

Это немного более многословно, но в отличие от решения поиска типов:

  • намерение разработчика более ясно
  • это будет работать с обоими функциональными компонентами и компонентами класса

Все это делает это решение наиболее перспективным: если вы решите перейти от классов к перехватчикам, вам не потребуется рефакторинг любого клиентского кода.

Ответ 2

Начиная с TypeScript 2.8, вы можете использовать условные типы, например:

interface MyComponentProps { bar: string; }
declare const MyComponent: React.Component<MyComponentProps>;

interface MyComponentClassProps { bar: string; }
declare const MyComponentClass: React.ComponentClass<MyComponentClassProps>;

interface MyStatelessComponentProps { bar: string; }
declare const MyStatelessComponent: React.StatelessComponent<MyStatelessComponentProps>;

Мы можем определить этих помощников:

type GetComponentProps<T> = T extends React.ComponentType<infer P> | React.Component<infer P> ? P : never

И использовать их так:

// $ExpectType MyComponentProps
type MyComponentPropsExtracted = GetComponentProps<typeof MyComponent>

// $ExpectType MyComponentClassProps
type MyComponentClassPropsExtracted = GetComponentProps<typeof MyComponentClass>

// $ExpectType MyStatelessComponentProps
type MyStatelessComponentPropsExtracted = GetComponentProps<typeof MyStatelessComponent>

Обновление 2018-12-31: теперь это доступно в официальных типах React через React.ComponentProps.

Ответ 3

Чтобы взять тип свойств из компонента

type Props = typeof MyComponent.defaultProps;

Вы можете спросить себя, почему я беру typeof из defaultProps, а не из propTypes. Чтобы объяснить, что позволяет взглянуть на файл определения

  interface ComponentClass<P> {
        new(props?: P, context?: any): Component<P, ComponentState>;
        propTypes?: ValidationMap<P>;
        contextTypes?: ValidationMap<any>;
        childContextTypes?: ValidationMap<any>;
        defaultProps?: P;
        displayName?: string;
    }

Как вы можете видеть, propTypes завернуты в ValidationMap, и нелегко получить необработанные типы. К счастью, defaultProps имеют необработанные типы

Ответ 4

С учетом компонента React:

import React, { ComponentType, StatelessComponent } from 'react';

const MyComponent: StatelessComponent<{ foo: string }> = props => <div>{props.foo}</div>;

Ты можешь сделать:

const getProps = function<Props> (_MyComponent: ComponentType<Props>): Props {
  return {} as Props;
};
const props = getProps(MyComponent);

// { foo: string; }
type MyComponentProps = typeof props;

Кроме того, вы можете увеличить титры React, чтобы добавить помощника GetComponentProps:

import React from 'react';

type NonNullable < T > = T & {};

declare module 'react' {
  // Add helper for accessing props type of given component. Based off of
  // https://github.com/DefinitelyTyped/DefinitelyTyped/pull/24182.
  type GetComponentProps < C extends ComponentType < any > > = NonNullable<C['_doNotUse_props']>;

  // We use interface merging to append properties to these types
  interface StatelessComponent<P = {}> {
    // eslint-disable-next-line camelcase
    _doNotUse_props?: P;
  }
  interface ComponentClass<P = {}> {
    // eslint-disable-next-line camelcase
    _doNotUse_props?: P;
  }
}

Использование выглядит следующим образом:

// { foo: string; }
type MyComponentProps = React.GetComponentProps<typeof MyComponent>;

Я изначально разместил это в https://github.com/DefinitelyTyped/DefinitelyTyped/pull/24182.

Ответ 5

Вы можете использовать import * as синтаксис:

import * as MC from './MyComponent';

type Props = MC.Props;