TypeScript + React

Подключение типов React

Существует много способов подключения библиотеки React. Выбор конкретного способа может зависеть от используемых технологий, языков программирования или личных предпочтений. Мы будем рассматривать наиболее популярный на данный момент способ - подключение npm-пакета с React. Npm-пакет с React не содержит типов для TypeScript. Типы для React находятся в отдельном npm-пакете @types/react. Его нужно установить отдельно. Исходные файлы этого пакета хранятся в репозитории DefinitelyTyped.

Импорт типов из модуля

TypeScript-модули могут экспортировать не только конструкции кода, но и типы.

/* SomeComponent.tsx */
export type Align = "left" | "right";

export interface SomeComponentProps {
    align: Align;
    value: string | null;
    onChange: (value: string | null) => void;
}

В других TypeScript-модулях можно импортировать типы, как и обычные конструкции кода.

/* OtherComponent.tsx */
import { Align, SomeComponentProps } from "{ path to module }";

const align: Align = "left";

В TypeScript >= 3.8 появилась возможность импортировать типы из другого модуля только на уровне типов. При обычном импорте, скрипт внутри импортируемого модуля исполняется. Когда из модуля импортируются только типы лишнее выполнение скрипта модуля может повлиять на производительность, а также создать неприятные сайд-эффекты. Для использования импорта типов нужно использовать конструкцию import type

При работе с React и использовании Babel обычно импорт React выглядит так:

В TypeScript без дополнительных настроек так сделать не получится. Нужно явно указывать alias:

Для TypeScript >= 2.7 в tsconfig.json можно выставить флажок compilerOptions.esModuleInterop: true. Тогда компилятор TypeScript будет генерировать дополнительные функции-хелперы в runtime, которые позволят использовать синтаксис экосистемы Babel. Подробнее об этой возможности можно узнать отдельно. Мы будем использовать явный импорт.

Функциональные компоненты

Долгое время в React функциональный компонент называли Stateless Component, потому что ему нельзя было добавить state. В React 16.8 появилась возможность использовать Hook-и. Это позволило использовать state в таких компонентах. Поэтому для версий React < 16.8 (а точнее для версии типов @types/react) для функциональных компонентов использовался тип StatelessComponent.

Тип StatelessComponent имеет один generic-параметр <P = {}>, которому можно указать тип props-ов. Тип props-ов обычно создается отдельно в виде интерфейса. Использование интерфейса позволит использовать наследование, что упростит переиспользование компонента.

В типах React есть тип React.SFC<P = {}> = React.StatelessComponent<P>. Тип SFC (stateless functional component) - это alias к типу StatelessComponent, который нужен для уменьшения длины строки.

В интерфейсах props-ов не нужно объявлять поле children. Оно уже объявлено в типе StatelessComponent. Даже если используется дефолтный generic для props, поле children все равно доступно.

В React >= 16.8 тип StatelessComponent заменяется на FunctionComponent, а SFC заменяется на FC. StatelessComponent и SFC не были удалены из типов React-а, но были помечены меткой @deprecated. С точки типов StatelessComponent<P = {}> = FunctionComponent<P>. То есть разница между этими двумя типами только в названии. Hook-и можно использовать не зависимо от того, какой тип вы используете.

Более того, для объявления функционального компонента вообще не обязательно использовать ни FunctionComponent, ни FC. Достаточно объявить функцию, которая будет возвращать объект типа React.ReactElement | null.

или

В таком подходе в props нужно явно объявлять children. По умолчанию тип у children?: React.ReactNode.

Тип для children можно заменить на любой другой. Например, его можно сделать обязательным или сделать его функцией. Для этого нужно указать в props поле children с нужным типом. Это будет работать и при использовании типов FunctionComponent и FC. Если при использовании компонента тип передаваемых children будет отличаться от типа, объявленного в props-ах - TypeScript выдаст ошибку.

В коде выше у функции App не указано возвращаемое значение. TypeScript автоматически выводит возвращаемое значение React.ReactElement у функциональных компонентов - поэтому тип можно не указывать.

У всех Hook-ов есть свои собственные типы. Например тип у Hook-а useState такой:

Чаще всего TypeScript может автоматически вывести generic S по заданному начальному значению.

Однако автоматический вывод не всегда возможен или не всегда подходит. Например, если в качестве начального значения используется null - не ясно какой тип автоматически выводить. Или, например, нужно использовать литеральный тип. В таких случаях можно задавать generic-параметр явно.

Компоненты-классы

Для создания компонента-класса нужно наследовать класс компонента от React.Component. Тип React.Component имеет 2 generic-параметра: первый для типа props, второй для типа state.

У этих generic-параметров есть дефолтные значения {}, поэтому их можно не указывать.

Также как и для функциональных компонентов в типе props не нужно указывать поле children. Это поле можно указать, чтобы переопределить тип.

Метод render() компонента-класса должен возвращает тип React.ReactNode. Этот тип позволяет возвращать не только элементы React и null, но и string, number, boolean и undefiend.

Для типизации параметров методов жизненного цикла компонента используются те же типы, которые были объявлены для generic-параметров тип React.Component.

Специально для работы с React в TypeScript сделали особую поддержку для deafultProps. Если у какого-то props-а есть дефолтные значение, то этот props указывать не обязательно. В таком случае в типе props-ов это поле можно пометить как необязательное:

Однако, необязательному полю можно в качестве значения передать undefined, что противоречит его типу. Поэтому разработчики TypeScript добавили возможность не помечать ? те props-ы, которые есть в deafultProps. При этом во время использования компонента такие поля можно не указывать.

Тип компонента

Иногда возникает необходимость использовать тип компонента. Например, для написания HOC-ов. Для этого в типах React есть специальные типы. Один из них - это React.ComponentType. В него входят тип функционального компонента и компонента-класса

Тип React.ComponentType имеет generic-параметр. Этот generic описывает тип props-ов. По умолчанию он равен {}. Поэтому, если в HOC myHOC передать React компонент с типом props-ов отличных от {} - TypeScript выдаст ошибку.

Для того, чтобы сделать HOC, который принимает компоненты с любым типом props-ов можно использовать any. Но лучше использовать generic-параметр и автоматический вывод типов generic-ов.

В коде выше generic TProps автоматически выводится TypeScript как { value: string }, когда в myHOC передается компонент Text. Используя generic для типов props-ов, можно накладывать ограничения на компоненты, которые можно передавать в HOC. Для этого нужно накладывать ограничение на тип props-ов.

В коде выше в HOC myHOC можно передавать только те компоненты, тип props-ов которых содержит поле color: string;.

Last updated

Was this helpful?