# TypeScript + React

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

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

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

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

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

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

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

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

const align: Align = "left";
```

В TypeScript >= 3.8 появилась возможность [импортировать типы из другого модуля только на уровне типов](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html#-type-only-imports-and-export). При обычном импорте, скрипт внутри импортируемого модуля исполняется. Когда из модуля импортируются только типы лишнее выполнение скрипта модуля может повлиять на производительность, а также создать неприятные сайд-эффекты. Для использования импорта типов нужно использовать конструкцию `import type`

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

const align: Align = "left";
```

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

```javascript
import React from "react"
```

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

```typescript
import * as React from "react"
```

Для TypeScript >= 2.7 в `tsconfig.json` можно выставить флажок `compilerOptions.esModuleInterop: true`. Тогда компилятор TypeScript будет генерировать дополнительные функции-хелперы в runtime, которые позволят использовать синтаксис экосистемы Babel. Подробнее об этой возможности можно узнать [отдельно](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-7.html#support-for-import-d-from-cjs-from-commonjs-modules-with---esmoduleinterop). Мы будем использовать явный импорт.

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

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

```typescript
const ColorText: React.StatelessComponent = () => {
   return <span>text</span>
}
```

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

```typescript
interface ColorTextProps {
   color: "red" | "green" | "blue";
}

const ColorText: React.StatelessComponent<ColorTextProps> = (props) => {
   return <span style={{color: props.color}}>text</span>
}
```

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

```typescript
interface ColorTextProps {
   color: "red" | "green" | "blue";
}

const ColorText: React.SFC<ColorTextProps> = (props) => {
   return <span style={{color: props.color}}>text</span>
}
```

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

```typescript
interface ColorTextProps {
   color: "red" | "green" | "blue";
}

const ColorText: React.SFC<ColorTextProps> = (props) => {
   return <span style={{color: props.color}}>{props.children}</span>
}
```

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

```typescript
interface ColorTextProps {
   color: "red" | "green" | "blue";
}

const ColorText: React.FC<ColorTextProps> = (props) => {
   return <span style={{color: props.color}}>{props.children}</span>
}
```

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

```typescript
interface MdashProps {
   color: "red" | "green" | "blue";
}

const Mdash = (props: MdashProps): React.ReactElement | null => {
   return <span style={{color: props.color}}>&mdash;</span>
}
```

или

```typescript
interface MdashProps {
   color: "red" | "green" | "blue";
}

function Mdash(props: MdashProps): React.ReactElement {
   return <span style={{color: props.color}}>&mdash;</span>
}
```

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

```typescript
interface ColorTextProps {
   color: "red" | "green" | "blue";
   children?: React.ReactNode
}

function ColorText(props: ColorTextProps): React.ReactElement {
     return <span style={{color: props.color}}>{props.children}</span>
}
```

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

```typescript
interface ColorTextProps {
    color: "red" | "green" | "blue";
    children: () => React.ReactNode;
}

const ColorText: React.FC<ColorTextProps> = (props) => {
    return <span style={{ color: props.color }}>{props.children()}</span>
}

function App() {
    return (
        <ColorText color="green">
            some text //Error: 'ColorText' components don't accept text as child elements. Text in JSX has the type 'string', but the expected type of 'children' is '(() => ReactNode)
        </ColorText>
    )
}
```

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

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

```typescript
type SetStateAction<S> = S | ((prevState: S) => S);
type Dispatch<A> = (value: A) => void;
function useState<S>(initialState: S | (() => S)): [S, Dispatch<SetStateAction<S>>];
```

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

```typescript
const [value, setValue] = React.useState(10);
```

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

```typescript
const [color, setColor] = React.useState<"red" | "green" | "blue">("red");
const [text, setText] = React.useState<string>(null);
```

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

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

```typescript
interface ColorTextProps {
   value: string | null;
   onChange: (value: string | null) => void;
}

interface ColorTextState {
   color: "red" | "green" | "blue";
}

class ColorText extends React.Component<ColorTextProps, ColorTextState> {
   ...
}
```

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

```typescript
class Comp1 extends React.Component {
   ...
}

class Comp2 extends React.Component<ColorTextProps> {
   ...
}

// Мы не можем указать 2-ой generic не указав 1-ый
class Comp3 extends React.Component<{}, ColorTextState> {
   ...
}
```

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

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

```typescript
class SomeComponent extends React.Component<{value: any}> {
   render(): React.ReactNode {
      const value = this.props.value;
      if(typeof value === "number") {
         return value + 10; // number
      }
      if(typeof value === "boolean") {
         return !value; // boolean
      }
      if(typeof value === "undefined") {
         return value; // undefiened
      }

      return JSON.stringify(value); // string
   }
}
```

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

```typescript
interface ColorTextProps {
   value: string;
}

interface ColorTextState {
   color: "red" | "green" | "blue";
}

class ColorText extends React.Component<ColorTextProps, ColorTextState> {
   componentDidUpdate(prevProps: ColorTextProps, prevState: ColorTextState): void {
      if(this.props.value !== prevProps.value || this.state.color !== prevState.color) {
         ...
      }
   }
   ...
}
```

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

```typescript
interface ColorTextProps {
   value: string;
   width?: number;
}
```

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

```typescript
interface ColorTextProps {
   value: string;
   width: number;
}

class ColorText extends React.Component<ColorTextProps> {
   static defaultProps = {
      width: 100
   }
   ...
}

function App() {
   return (
      <ColorText value="value">Some message</ColorText>
   )
}
```

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

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

```typescript
function myHOC(Component: React.ComponentType) {
   ...
}

class Some extends React.Component {
    render() {
        return null
    }
}

myHOC(() => <div>lalaka</div>);
myHOC(Some);
```

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

```typescript
const Text = (props: { value: string }) => {
    return <span>{props.value}</span>;
}

myHOC(Text); // Error: Argument of type '(props: { value: string; }) => JSX.Element' is not assignable to parameter of type 'ComponentType<{}>'.
```

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

```typescript
function myHOC<TProps>(Component: React.ComponentType<TProps>) {
   ...
}

myHOC(Text);
```

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

```typescript
interface WithColor {
    color: string;
}

function myHOC<TProps extends WithColor>(Component: React.ComponentType<TProps>) {
   ...
}

const Text = (props: { value: string }) => {
    return <span>{props.value}</span>;
}

const ColorText = (props: { value: string, color: string }) => {
    return <span style={{color: props.color}}>{props.value}</span>;
}

myHOC(ColorText);
myHOC(Text); // Error: Argument of type '(props: { value: string; }) => JSX.Element' is not assignable to parameter of type 'ComponentType<WithColor>'.
```

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


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://race-timo.gitbook.io/typescript/ts-react.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
