HOW TO React

Использование TypeScript

TypeScript — популярный способ добавления определений типов в кодовые базы JavaScript. TypeScript по умолчанию поддерживает JSX, и вы можете получить полную поддержку React Web, добавив в свой проект аннотации @types/react и @types/react-dom.

Вы узнаете про

  • TypeScript с компонентами React
  • Примеры типизации с использованием хуков
  • Распространенные типы из @types/react
  • Дополнительные ресурсы для обучения

Установка

Все производственные фреймворки React поддерживают использование TypeScript. Следуйте инструкциям по установке для конкретного фреймворка:

Добавление TypeScript в существующий проект React

Чтобы установить последнюю версию определений типов React:


npm install --save-dev @types/react @types/react-dom

В файле tsconfig.json необходимо задать следующие параметры компилятора:

  1. dom должен быть включен в lib (Примечание: если параметр lib не указан, dom включается по умолчанию).
  2. jsx должен быть установлен в одно из допустимых значений. Для большинства приложений достаточно preserve. Если вы публикуете библиотеку, обратитесь к jsx документации для выбора подходящего значения.

TypeScript с компонентами React

Каждый файл, содержащий JSX, должен иметь расширение .tsx. Это расширение, специфичное для TypeScript, которое сообщает TypeScript, что данный файл содержит JSX.

Написание TypeScript с React очень похоже на написание JavaScript с React. Ключевое отличие при работе с компонентом заключается в том, что вы можете задавать типы для пропсов вашего компонента. Эти типы можно использовать для проверки корректности и предоставления встроенной документации в редакторах.

Возьмем компонент MyButton из руководства «Быстрый старт» и добавим тип, описывающий title для кнопки:

function MyButton({ title }: { title: string }) {
  return (
    <button>{title}</button>
  );
}

export default function MyApp() {
  return (
    <div>
      <h1>Welcome to my app</h1>
      <MyButton title="Я кнопка" />
    </div>
  );
}

Этот встроенный синтаксис — самый простой способ задать типы для компонента, хотя, как только у вас появляется несколько полей для описания, он может стать неудобным. Вместо этого вы можете использовать interface или type для описания пропсов компонента:

interface MyButtonProps {
  /** Текст, который будет отображаться внутри кнопки */
  title: string;
  /** Можно ли взаимодействовать с кнопкой */
  disabled: boolean;
}

function MyButton({ title, disabled }: MyButtonProps) {
  return (
    <button disabled={disabled}>{title}</button>
  );
}

export default function MyApp() {
  return (
    <div>
      <h1>Добро пожаловать в мое приложение</h1>
      <MyButton title="Я кнопка с отключенной функцией" disabled={true}/>
    </div>
  );
}

Тип, описывающий пропсы вашего компонента, может быть настолько простым или сложным, насколько вам нужно, хотя они должны быть объектным типом, описанным с помощью type или interface. Вы можете узнать о том, как TypeScript описывает объекты, в Типы объектов, но вам также может быть интересно использовать объединенных типов для описания свойства, которое может быть одним из нескольких различных типов, а также руководство Создание типов из типов для более сложных случаев использования.

Примеры хуков

Определения типов из @types/react включают типы для встроенных хуков, поэтому вы можете использовать их в своих компонентах без какой-либо дополнительной настройки. Они созданы с учетом кода, который вы пишете в своих компонентах, поэтому в большинстве случаев вы будете получать выведенные типы и, в идеале, вам не придется заниматься мелочами, связанными с указанием типов.

Тем не менее, мы можем рассмотреть несколько примеров того, как указывать типы для хуков.

useState

Хук useState будет повторно использовать значение, переданное в качестве начального состояния, чтобы определить, каким должен быть тип этого значения. Например:

// Вывести тип как «boolean»
const [enabled, setEnabled] = useState(false);

Это присвоит enabled тип boolean, а setEnabled будет функцией, принимающей либо аргумент типа boolean, либо функцию, возвращающую boolean. Если вы хотите явно указать тип для состояния, вы можете сделать это, указав аргумент типа при вызове useState:

// Явно задать тип как «boolean»
const [enabled, setEnabled] = useState<boolean>(false);

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

type Status = «idle» | «loading» |success| «error»;

const [status, setStatus] = useState<Status>(«idle»);

Или, вы можете сгруппировать связанные состояния в объект и описать различные возможности с помощью типов объектов:

type RequestState =
  | { status:idle“ }
  | { status:loading“ }
  | { status:success“, data: any }
  | { status:error“, error: Error };

const [requestState, setRequestState] = useState<RequestState>({ status: „idle“ });

useReducer

Хук useReducer — это более сложный хук, который принимает функцию-редуктор и начальное состояние. Типы для функции-редуктора выводятся из начального состояния. Вы можете опционально предоставить аргумент типа при вызове useReducer, чтобы задать тип для состояния, но часто лучше вместо этого задать тип в начальном состоянии:

import {useReducer} from 'react';interface State {  count: number};type CounterAction =  | { type: "reset" }  | { type: "setCount"; value: State["count"] }const initialState: State = { count: 0 };function stateReducer(state: State, action: CounterAction): State {  switch (action.type) {    case "reset":      return initialState;    case "setCount":      return { ...state, count: action.value };    default:      throw new Error("Unknown action");  }}export default function App() {  const [state, dispatch] = useReducer(stateReducer, initialState);  const addFive = () => dispatch({ type: "setCount", value: state.count + 5 });  const reset = () => dispatch({ type: "reset" });  return (    <div>      <h1>Welcome to my counter</h1>      <p>Count: {state.count}</p>      <button onClick={addFive}>Add 5</button>      <button onClick={reset}>Reset</button>    </div>  );}
Preview

Мы используем TypeScript в нескольких ключевых местах:

  • interface State описывает структуру состояния редуктора.
  • type CounterAction описывает различные действия, которые могут быть отправлены в редуктор.
  • const initialState: State предоставляет тип для начального состояния, а также тип, который по умолчанию используется useReducer.
  • stateReducer(state: State, action: CounterAction): State задает типы для аргументов и возвращаемого значения функции редуктора.

Более явной альтернативой заданию типа для initialState является предоставление аргумента типа для useReducer:

import { stateReducer, State } from './your-reducer-implementation';

const initialState = { count: 0 };

export default function App() {
  const [state, dispatch] = useReducer<State>(stateReducer, initialState);
}

useContext

Хук useContext — это способ передачи данных вниз по дереву компонентов без необходимости передачи пропсов через компоненты. Он используется путем создания компонента-провайдера и, зачастую, путем создания хука для использования значения в дочернем компоненте.

Тип значения, предоставляемого контекстом, определяется на основе значения, переданного при вызове createContext:

import { createContext, useContext, useState } from 'react';type Theme = "light" | "dark" | "system";const ThemeContext = createContext<Theme>("system");const useGetTheme = () => useContext(ThemeContext);export default function MyApp() {  const [theme, setTheme] = useState<Theme>('light');  return (    <ThemeContext value={theme}>      <MyComponent />    </ThemeContext>  )}function MyComponent() {  const theme = useGetTheme();  return (    <div>      <p>Current theme: {theme}</p>    </div>  )}
Preview

Этот прием работает, когда у вас есть смысловое значение по умолчанию, но иногда бывают случаи, когда его нет, и в таких случаях null может показаться разумным значением по умолчанию. Однако, чтобы система типов могла понять ваш код, вам нужно явно задать ContextShape | null в createContext.

Это приводит к тому, что вам нужно удалить | null из типа для потребителей контекста. Мы рекомендуем, чтобы хук выполнял проверку на наличие этого значения во время выполнения и выбрасывал ошибку, если его нет:

import { createContext, useContext, useState, useMemo } from „react“;

// Это упрощенный пример, но здесь можно представить и более сложный объект
type ComplexObject = {
  kind: string
};

// Контекст создается с `| null` в типе, чтобы точно отразить значение по умолчанию.
const Context = createContext<ComplexObject | null>(null);

// `| null` будет удалено посредством проверки в хуке.
const useGetComplexObject = () => {
  const object = useContext(Context);
  if (!object) { throw new Error(«useGetComplexObject must be used within a Provider») }
  return object;
}

export default function MyApp() {
  const object = useMemo(() => ({ kind: «complex» }), []);

  return (
    <Context value={object}>
      <MyComponent />
    </Context>
  )
}

function MyComponent() {
  const object = useGetComplexObject();

  return (
    <div>
      <p>Текущий объект: {object.kind}</p>
    </div>
  )
}

useMemo

React Compiler
автоматически запоминает значения и функции, уменьшая необходимость в ручных вызовах useMemo. Вы можете использовать компилятор для автоматической обработки запоминания.

Хук useMemo создает или повторно получает доступ к запомненному значению из вызова функции, повторно запуская функцию только в том случае, если изменились зависимости, переданные в качестве второго параметра. Результат вызова хука выводится из возвращаемого значения функции в первом параметре. Вы можете сделать это более явно, предоставив аргумент типа хуку.

// Тип visibleTodos выводится из возвращаемого значения filterTodos
const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);

useCallback

React Compiler автоматически мемоизирует значения и функции, уменьшая необходимость в ручных вызовах useCallback. Вы можете использовать компилятор для автоматической обработки мемоизации.

useCallback предоставляет стабильную ссылку на функцию, пока зависимости, переданные во второй параметр, остаются неизменными. Как и в случае с useMemo, тип функции выводится из возвращаемого значения функции в первом параметре, и вы можете сделать его более явным, указав аргумент типа в хуке.

const handleClick = useCallback(() => {
  // ...
}, [todos]);

При работе в строгом режиме TypeScript useCallback требует добавления типов для параметров в вашем обратном вызове. Это связано с тем, что тип обратного вызова выводится из возвращаемого значения функции, и без параметров тип не может быть полностью понят.

В зависимости от ваших предпочтений в стиле кода, вы можете использовать функции *EventHandler из типов React, чтобы указать тип обработчика события одновременно с определением обратного вызова:

import { useState, useCallback } from „react“;

export default function Form() {
  const [value, setValue] = useState(«Change me»);

  const handleChange = useCallback<React.ChangeEventHandler<HTMLInputElement>>((event) => {
    setValue(event.currentTarget.value);
  }, [setValue])

  return (
    <>
      <input value={value} onChange={handleChange} />
      <p>Value: {value}</p>
    </>
  );
}

Полезные типы

Существует довольно обширный набор типов из пакета @types/react, с которым стоит ознакомиться, когда вы освоите взаимодействие React и TypeScript. Их можно найти в папке React на сайте DefinitelyTyped. Здесь мы рассмотрим несколько наиболее распространенных типов.

События DOM

При работе с событиями DOM в React тип события часто можно определить по обработчику событий. Однако, если вы хотите извлечь функцию для передачи в обработчик событий, вам нужно явно указать тип события.

import { useState } from 'react';export function Example8() {  const [value, setValue] = useState("Change me");  function handleChange(event: React.ChangeEvent<HTMLInputElement>) {    setValue(event.currentTarget.value);  }  return (    <>      <input value={value} onChange={handleChange} />      <p>Value: {value}</p>    </>  );}
Preview

В React предоставляется множество типов событий — полный список можно найти здесь основанный на наиболее популярных событиях из DOM.

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

Если вам нужно использовать событие, которое не входит в этот список, вы можете использовать тип React.SyntheticEvent, который является базовым типом для всех событий.

Дочерние элементы

Существует два распространенных способа описания дочерних элементов компонента. Первый — использовать тип React.ReactNode, который представляет собой объединение всех возможных типов, которые могут передаваться в качестве дочерних элементов в JSX:

interface ModalRendererProps {
  title: string;
  children: React.ReactNode;
}

Это очень широкое определение дочерних элементов. Второй способ — использовать тип React.ReactElement, который включает только элементы JSX, а не примитивы JavaScript, такие как строки или числа:

interface ModalRendererProps {
  title: string;
  children: React.ReactElement;
}

Обратите внимание, что в TypeScript нельзя описать, что дочерние элементы являются определенным типом элементов JSX, поэтому вы не можете использовать систему типов для описания компонента, который принимает только дочерние элементы <li>.

Вы можете увидеть примеры как React.ReactNode, так и React.ReactElement с проверкой типов в этом TypeScript playground.

Свойства стиля

При использовании встроенных стилей в React вы можете использовать React.CSSProperties для описания объекта, передаваемого свойству style. Этот тип представляет собой объединение всех возможных свойств CSS и является хорошим способом гарантировать передачу допустимых свойств CSS свойству style, а также для автозаполнения в редакторе.

interface MyComponentProps {
style: React.CSSProperties;

}

Дальнейшее обучение

В этом руководстве рассмотрены основы использования TypeScript с React, но есть еще много чего узнать. Отдельные страницы API в документации могут содержать более подробную информацию о том, как использовать их с TypeScript.

Мы рекомендуем следующие ресурсы:

  • Руководство по TypeScript — это официальная документация по TypeScript, охватывающая большинство ключевых языковых возможностей.

  • Примечания к выпуску TypeScript подробно описывают новые функции.

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

  • Discord-сервер сообщества TypeScript — отличное место для вопросов и получения помощи по проблемам TypeScript и React.

On this page