Skip to main content

Начало работы React Redux с TypeScript

Чему вы научитесь?
  • Как установить и использовать Redux Toolkit и 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 и последующем импортировании в других файлах.

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). Это позволит вам импортировать их в любой компонент, который должен использовать хуки, и избежать потенциальную проблему с импортом из-за циклической зависимости.

app/hooks.ts
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 может корректно обработать эти типы. Это может потребоваться для написания селекторов.

features/counter/counterSlice.ts
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.

features/counter/Counter.tsx
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.