Начало работы React Redux с TypeScript
Чему вы научитесь?
- Как установить и использовать Redux Toolkit и React Redux вместе с TypeScript
Предварительные требования
- Знание 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 компонентами.
Шаблон Redux+TS для Create-React-App содержит настроенный проект.
Информация
В недавно обновлённой основной версии @types/react@18
изменилось определение компонентов, был удален пропс children
, существовавший по умолчанию. В случае наличия несколько копий @types/react
в вашем проекте, это будет вызывать ошибки. Для исправления воспользуйтесь вашим менеджером пакетов, чтобы привести @types/react
к единственной версии. Детали:
https://github.com/facebook/react/issues/24304#issuecomment-1094565891
Установка проекта
Определение корневого состояние и типов для отправки (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 = ReturnType<typeof store.getState>
// Выведенные типы: {posts: PostsState, comments: CommentsState, users: UsersState}
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: TypedUseSelectorHook<RootState> = useSelector
Использование в приложении
Части состояния и типы действий (action)
Каждый файл среза(части состояния/slice) должен определять тип его начального состояния, таким образом createSlice
сможет правильно определить тип state
при каждом редюсере (reducer).
Все сгенерированные действия должны быть определены используя тип PayloadAction<T>
из Redux Toolkit, который берёт тип поля action.payload
как аргумент для шаблона (<T>
).
Здесь вы можете безопасно импортировать тип 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: PayloadAction<number>) => {
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<T>
, который вы передаёте в функцию редюсера(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.