Работа с TypeScript
Начиная с 8-ой версии, React-Redux полностью написан на TypeScript, тем самым типизация включена в распространяемый пакет. Вдобавок, типизация содержит в себе несколько помощников (helpers) для упрощения написания типобезопасных интерфейсов между Redux хранилищем(store) и React компонентами.
Информация
В недавно обновлённой основной версии @types/react@18
изменилось определение компонентов, был удален пропс children
, существовавший по умолчанию. В случае наличия несколько копий @types/react
в вашем проекте, это будет вызывать ошибки. Для исправления воспользуйтесь вашим менеджером пакетов, чтобы привести @types/react
к единственной версии. Детали:
https://github.com/facebook/react/issues/24304#issuecomment-1094565891
Стандартная настройка проекта с Redux Toolkit и TypeScript
Мы предполагаем, что типичный Redux проект использует Redux Toolkit вместе с React Redux.
Redux Toolkit (RTK) — стандартный подход для написания современной Redux логики. RTK написан на TypeScript и его API разработано с целью дать хороший опыт вместе с TypeScript.
Шаблон Redux+TS для Create-React-App содержит настроенный проект.
Определение корневого состояние и типов для отправки (dispatch)
Использование configureStore не нуждается в дополнительной типизации. Несмотря на это вам следуют извлечь типы 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
Ручная типизация хуков
Мы рекомендуем использовать хуки useAppSelector
и useAppDispatch
, показанные выше. Если вы предпочитаете их не использовать, то здесь мы покажем, как типизировать сами хуки.
Типизация хука useSelector
Когда вы пишете функцию селектор в useSelector
, вам следует явно определить тип параметра state
. Так Typescript сможет вывести тип возвращаемого значения функции селектора, который в свою очередь будет использован как возвращаемый тип самого хука useSelector
:
interface RootState {
isOn: boolean
}
// TS выводит тип: (state: RootState) => boolean
const selectIsOn = (state: RootState) => state.isOn
// TS выводит `isOn` как boolean
const isOn = useSelector(selectIsOn)
This can also be done inline as well:
const isOn = useSelector((state: RootState) => state.isOn)
Типизация хука useDispatch
По умолчанию возвращаемое значение useDispatch
- стандартный тип Dispatch
, определённый в основных типах Redux, следовательно типизация не потребуется:
const dispatch = useDispatch()
Если у вы изменяете тип Dispatch
, вы можете использовать его явно:
// store.ts
export type AppDispatch = typeof store.dispatch
// MyComponent.tsx
const dispatch: AppDispatch = useDispatch()
Типизация компонента высшего порядка connect
Автовыведение типов подключенных пропсов
connect
содержит 2 функции, вызывающиеся одна за другой. Первая функция в качестве аргументов принимает mapState
и mapDispatch
и возвращает вторую функцию. Вторая функция принимает компонент для того, чтобы обёрнуть его, и возвращает новый компонент обёртку, который принимает пропсы из mapState
и mapDispatch
. Обычно обе функции вызываются вместе: connect(mapState, mapDispatch)(MyComponent)
.
В пакет включен помощник для типов, ConnectedProps
, который может извлечь типы возвращаемых значений mapStateToProps
и mapDispatchToProps
из первой функции. Это означает, что при разъединении вызова connect
на 2 шага, все "пропсы из Redux" могут быть выведены автоматически, без необходимости писать их вручную. Этот подход может показаться непривычным, если вы использовали React-Redux ранее, это значительно упрощает объявления типов.
import { connect, ConnectedProps } from 'react-redux'
interface RootState {
isOn: boolean
}
const mapState = (state: RootState) => ({
isOn: state.isOn,
})
const mapDispatch = {
toggleOn: () => ({ type: 'TOGGLE_IS_ON' }),
}
const connector = connect(mapState, mapDispatch)
// Выведенный тип будет выглядеть так:
// {isOn: boolean, toggleOn: () => void}
type PropsFromRedux = ConnectedProps<typeof connector>
Возвращаемый тип из ConnectedProps
может быть использован для типизации ваших пропсов.
interface Props extends PropsFromRedux {
backgroundColor: string
}
const MyComponent = (props: Props) => (
<div style={{ backgroundColor: props.backgroundColor }}>
<button onClick={props.toggleOn}>
Toggle is {props.isOn ? 'ON' : 'OFF'}
</button>
</div>
)
export default connector(MyComponent)
В силу того, что типы могут быть определены в любом порядке, при желании вы всё ещё можете объявить ваш компонент перед объявлением функции connector.
// Альтернативно объявляем `type Props = PropsFromRedux & {backgroundColor: string}`
interface Props extends PropsFromRedux {
backgroundColor: string;
}
const MyComponent = (props: Props) => /* также как выше */
const connector = connect(/* также как выше */)
type PropsFromRedux = ConnectedProps<typeof connector>
export default connector(MyComponent)
Ручная типизация connect
Компонент высшего порядка connect
несколько сложно типизировать, ведь у него 3 источника пропсов: mapStateToProps
, mapDispatchToProps
и пропсы, переданные из родительского компонента. Здесь полный пример, как выглядит его типизация вручную:
import { connect } from 'react-redux'
interface StateProps {
isOn: boolean
}
interface DispatchProps {
toggleOn: () => void
}
interface OwnProps {
backgroundColor: string
}
type Props = StateProps & DispatchProps & OwnProps
const mapState = (state: RootState) => ({
isOn: state.isOn,
})
const mapDispatch = {
toggleOn: () => ({ type: 'TOGGLE_IS_ON' }),
}
const MyComponent = (props: Props) => (
<div style={{ backgroundColor: props.backgroundColor }}>
<button onClick={props.toggleOn}>
Toggle is {props.isOn ? 'ON' : 'OFF'}
</button>
</div>
)
// Типичное использование: функция `connect` вызывается после определения компонента
export default connect<StateProps, DispatchProps, OwnProps>(
mapState,
mapDispatch
)(MyComponent)
Также возможно несколько сократить это, выведением типов mapState
и mapDispatch
:
const mapState = (state: RootState) => ({
isOn: state.isOn,
})
const mapDispatch = {
toggleOn: () => ({ type: 'TOGGLE_IS_ON' }),
}
type StateProps = ReturnType<typeof mapState>
type DispatchProps = typeof mapDispatch
type Props = StateProps & DispatchProps & OwnProps
Тем не менее, выведение типа mapDispatch
так не будет работать, если он определен как объект, а также ссылается на thunk'и.
Рекомендации
API хуков в целом проще использовать со статическими типами. Если вы ищете наиболее простое решение для использования статических типов с React-Redux, посмотриет API хуков.
Если вы используете connect
, мы рекомендуем использовать ConnectedProps<T>
подход для выведения типов пропсов из Redux, так как это требует наименьшего количества явных объявлений типа.
Ресурсы
Для дополнительной информации посмотрите:
- Документация Redux: Использование с Typescript: Примеры использования Redux Toolkit, ядра Redux и React Redux с TypeScript
- Документация Redux Toolkit: Быстрый старт с Typescript: показывает, как использовать RTK и API React-Redux хуков с TypeScript
- React+TypeScript Cheatsheet: доступный гайд для использования React с TypeScript
- React + Redux в гайде TypeScript: обширная информация о шаблонах использования React и Redux с TypeScript