Использование 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 необходимо задать следующие параметры компилятора:
domдолжен быть включен вlib(Примечание: если параметрlibне указан,domвключается по умолчанию).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> );}Мы используем 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> )}Этот прием работает, когда у вас есть смысловое значение по умолчанию, но иногда бывают случаи, когда его нет, и в таких случаях 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> </> );}В 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.
Настройка редактора
Правильно настроенный редактор может сделать код более понятным и ускорить его написание. Он даже может помочь вам обнаружить ошибки во время написания! Если вы впервые настраиваете редактор или хотите улучшить свой текущий редактор, у нас есть несколько рекомендаций.
Инструменты разработчика React
Используйте инструменты разработчика React для проверки компонентов React, редактирования свойств и состояния, а также выявления проблем с производительностью.