- Начало работы React Redux с TypeScript
- Установка проекта
- Определение корневого состояние и типов для отправки (dispatch)
- Определяем типизированные хуки
- Использование в приложении
- Части состояния и типы действий (action)
- Использование типизированных хуков в компонентах
- Что дальше?
- Redux Toolkit TypeScript Quick Start
- Project Setup
- Define Root State and Dispatch Types
- Define Typed Hooks
- Application Usage
- Define Slice State and Action Types
- Use Typed Hooks in Components
- Full Counter App Example
- What’s Next?
Начало работы React Redux с TypeScript
Приветствуем в начальном руководстве по React Redux с TypeScript! Это руководство будет вкратце покажет, как использовать TypeScript с Redux Toolkit.
Эта страница будет акцентировать внимание только на аспекты работы с TypeScript. Для понимания работы Redux и изучения примеров его использования посмотрите руководства в документации самого Redux.
React-Redux и Redux Toolkit написаны на TypeScript, следовательно типизация уже встроена.
React Redux имеет определения типов в отдельном @types/react-redux пpm пакете с типами. В дополнение к типизации библиотечных функций,экспортируются помощники для упрощения написания типобезопасных интерфейсов между вашим Redux хранилищем и React компонентами.
В недавно обновлённой основной версии @types/react@18 изменилось определение компонентов, был удален пропс children , существовавший по умолчанию. В случае наличия несколько копий @types/react в вашем проекте, это будет вызывать ошибки. Для исправления воспользуйтесь вашим менеджером пакетов, чтобы привести @types/react к единственной версии. Детали:
Установка проекта
Определение корневого состояние и типов для отправки (dispatch)
Redux Toolkit configureStore API не нуждается в установке дополнительных типов. Несмотря на это вам следуют извлечь типы RootState и Dispatch , чтобы на них можно было ссылаться по мере необходимости. Извлечение этих типов из хранилища(store) означает, что они будут корректно обновляться по мере добавления новых частей хранилища(store) или при модификации настроек middleware.
Так как они являются типами, можно не переживать о безопасности при экспорте их из файла с настройкой хранилища app/store.ts и последующем импортировании в других файлах.
import configureStore > from '@reduxjs/toolkit' // . const store = configureStore( reducer: posts: postsReducer, comments: commentsReducer, users: usersReducer, >, >) // Выведение типов `RootState` и `AppDispatch` из хранилища export type RootState = ReturnTypetypeof store.getState> // Выведенные типы: export type AppDispatch = typeof store.dispatch
Определяем типизированные хуки
Пускай, имеется возможность импортировать типы RootState и AppDispatch в каждый компонент, лучше создать типизированные версии хуков useDispatch и useSelector . Это важно по нескольким причинам:
- useSelector избавляет вас от необходимости каждый раз печатать (state: RootState)
- useDispatch : тип Dispatch по умолчанию не знаком с thunks. С целью корректной отправки thunks, вам необходимо использовать специальный тип AppDispatch из хранилища (store), который включает типы из thunk middleware и использует их вместе с useDispatch . С помощью типизированного хука useDispatch можно забыть о необходимости импортировать AppDispatch .
Поскольку это переменные, а не типы, важно определить их в отдельном файле, таком как app/hooks.ts , а не в файле настройки хранилища(store). Это позволит вам импортировать их в любой компонент, который должен использовать хуки, и избежать потенциальную проблему с импортом из-за циклической зависимости.
import TypedUseSelectorHook, useDispatch, useSelector > from 'react-redux' import type RootState, AppDispatch > from './store' // Используйте во всем приложении вместо обычных `useDispatch` и `useSelector` export const useAppDispatch: () => AppDispatch = useDispatch export const useAppSelector: TypedUseSelectorHookRootState> = useSelector
Использование в приложении
Части состояния и типы действий (action)
Каждый файл среза(части состояния/slice) должен определять тип его начального состояния, таким образом createSlice сможет правильно определить тип state при каждом редюсере (reducer).
Все сгенерированные действия должны быть определены используя тип PayloadAction из Redux Toolkit, который берёт тип поля action.payload как аргумент для шаблона ( ).
Здесь вы можете безопасно импортировать тип RootState из файла хранилища (store). Это циклический импорт, но компилятор TypeScript может корректно обработать эти типы. Это может потребоваться для написания селекторов.
import createSlice, PayloadAction > from '@reduxjs/toolkit' import type RootState > from '../../app/store' // Определяем тип части состояния(среза/slice) interface CounterState value: number > // Определение начального состояния, используя тип const initialState: CounterState = value: 0, > export const counterSlice = createSlice( name: 'counter', // `createSlice` выведет тип состояния из аргумента `initialState` initialState, reducers: increment: (state) => state.value += 1 >, decrement: (state) => state.value -= 1 >, // Использование типа PayloadAction для объявления содержимого `action.payload` incrementByAmount: (state, action: PayloadActionnumber>) => state.value += action.payload >, >, >) // Сгенерированные Создатели Действий/ action creators export const increment, decrement, incrementByAmount > = counterSlice.actions // Весь остальной код может использовать тип `RootState` export const selectCount = (state: RootState) => state.counter.value export default counterSlice.reducer
Сгенерированные Создатели Действий будут корректно типизированы для принятия аргумента payload , основанного на типе PayloadAction , который вы передаёте в функцию редюсера(reducer). Например, incrementByAmount требует number в качестве аргумента.
В некоторых случаях TypeScript может излишне сузить тип начального значения. Если это случилось, то используйте as вместо объявления нового типа:
// Преобразование состояние вместо объявления нового типа const initialState = value: 0, > as CounterState
Использование типизированных хуков в компонентах
В файлах компонентов импортируйте типизированные хуки вместо стандартных хуков из React-Redux.
import React, useState > from 'react' import useAppSelector, useAppDispatch > from 'app/hooks' import decrement, increment > from './counterSlice' export function Counter() // Аргумент `state` уже корректно типизирован как `RootState` const count = useAppSelector((state) => state.counter.value) const dispatch = useAppDispatch() // логика отрисовки. >
Что дальше?
Посмотрите «использование с TypeScript», чтобы узнать подробности использования Redux Toolkit API с TypeScript.
Redux Toolkit TypeScript Quick Start
Welcome to the Redux Toolkit TypeScript Quick Start tutorial! This tutorial will briefly show how to use TypeScript with Redux Toolkit and React-Redux.
This page focuses on just how to set up the TypeScript aspects . For explanations of what Redux is, how it works, and full examples of how to use Redux Toolkit, see the tutorials linked in the «Tutorials Index» page.
Redux Toolkit is already written in TypeScript, so its TS type definitions are built in.
React Redux is also written in TypeScript as of version 8, and also includes its own type definitions.
The Redux+TS template for Create-React-App comes with a working example of these patterns already configured.
Project Setup
Define Root State and Dispatch Types
Redux Toolkit’s configureStore API should not need any additional typings. You will, however, want to extract the RootState type and the Dispatch type so that they can be referenced as needed. Inferring these types from the store itself means that they correctly update as you add more state slices or modify middleware settings.
Since those are types, it’s safe to export them directly from your store setup file such as app/store.ts and import them directly into other files.
import configureStore > from '@reduxjs/toolkit' // . const store = configureStore( reducer: posts: postsReducer, comments: commentsReducer, users: usersReducer > >) // Infer the `RootState` and `AppDispatch` types from the store itself export type RootState = ReturnTypetypeof store.getState> // Inferred type: export type AppDispatch = typeof store.dispatch
Define Typed Hooks
While it’s possible to import the RootState and AppDispatch types into each component, it’s better to create typed versions of the useDispatch and useSelector hooks for usage in your application. This is important for a couple reasons:
- For useSelector , it saves you the need to type (state: RootState) every time
- For useDispatch , the default Dispatch type does not know about thunks. In order to correctly dispatch thunks, you need to use the specific customized AppDispatch type from the store that includes the thunk middleware types, and use that with useDispatch . Adding a pre-typed useDispatch hook keeps you from forgetting to import AppDispatch where it’s needed.
Since these are actual variables, not types, it’s important to define them in a separate file such as app/hooks.ts , not the store setup file. This allows you to import them into any component file that needs to use the hooks, and avoids potential circular import dependency issues.
import TypedUseSelectorHook, useDispatch, useSelector > from 'react-redux' import type RootState, AppDispatch > from './store' // Use throughout your app instead of plain `useDispatch` and `useSelector` export const useAppDispatch: () => AppDispatch = useDispatch export const useAppSelector: TypedUseSelectorHookRootState> = useSelector
Application Usage
Define Slice State and Action Types
Each slice file should define a type for its initial state value, so that createSlice can correctly infer the type of state in each case reducer.
All generated actions should be defined using the PayloadAction type from Redux Toolkit, which takes the type of the action.payload field as its generic argument.
You can safely import the RootState type from the store file here. It’s a circular import, but the TypeScript compiler can correctly handle that for types. This may be needed for use cases like writing selector functions.
import createSlice, PayloadAction > from '@reduxjs/toolkit' import type RootState > from '../../app/store' // Define a type for the slice state export interface CounterState value: number > // Define the initial state using that type const initialState: CounterState = value: 0 > export const counterSlice = createSlice( name: 'counter', // `createSlice` will infer the state type from the `initialState` argument initialState, reducers: increment: state => state.value += 1 >, decrement: state => state.value -= 1 >, // Use the PayloadAction type to declare the contents of `action.payload` incrementByAmount: (state, action: PayloadActionnumber>) => state.value += action.payload > > >) export const increment, decrement, incrementByAmount > = counterSlice.actions // Other code such as selectors can use the imported `RootState` type export const selectCount = (state: RootState) => state.counter.value export default counterSlice.reducer
The generated action creators will be correctly typed to accept a payload argument based on the PayloadAction type you provided for the reducer. For example, incrementByAmount requires a number as its argument.
In some cases, TypeScript may unnecessarily tighten the type of the initial state. If that happens, you can work around it by casting the initial state using as , instead of declaring the type of the variable:
// Workaround: cast state instead of declaring variable type const initialState = value: 0 > as CounterState
Use Typed Hooks in Components
In component files, import the pre-typed hooks instead of the standard hooks from React-Redux.
import React from 'react' import useAppSelector, useAppDispatch > from 'app/hooks' import decrement, increment > from './counterSlice' export function Counter() // The `state` arg is correctly typed as `RootState` already const count = useAppSelector(state => state.counter.value) const dispatch = useAppDispatch() // omit rendering logic >
Full Counter App Example
Here’s the complete TS counter application as a running CodeSandbox:
What’s Next?
We recommend going through the full «Redux Essentials» tutorial, which covers all of the key pieces included in Redux Toolkit, what problems they solve, and how to use them to build real-world applications.
You may also want to read through the «Redux Fundamentals» tutorial, which will give you a complete understanding of how Redux works, what Redux Toolkit does, and how to use it correctly.
Finally, see the «Usage with TypeScript» page for extended details on how to use Redux Toolkit’s APIs with TypeScript.