TypeScript укрепляет свои позиции в сообществе JS, и многие популярные компании начали использовать его со своим клиентским интерфейсом на основе реакции для проверки типов. Давайте посмотрим, как мы можем использовать TypeScript с React и Redux.
Добавление TypeScript в существующий проект create-react-app
Если вы хотите добавить TypeScript в существующее приложение, установите TypeScript и другие необходимые типы.
npm install --save typescript @types/node @types/react @types/react-dom @types/jest
Затем нам нужно переименовать файлы в .ts
или .tsx
, а затем запустить сервер. Это автоматически создаст файл tsconfig.json
.
Использование TypeScript с действиями
export interface IStartFetchPostsAction extends Action<’StartFetchPosts’> {} export interface IFetchPostsSuccessAction extends Action<’FetchPostsSuccess’> { posts: IPost[]; } export type PostActions = | IStartFetchPostsAction | IFetchPostsSuccessAction
Эти типы действий расширяют общий тип Action
, поставляемый с библиотекой Redux. Это гарантирует, что мы правильно установили свойство type
при использовании действий в нашем коде.
Как видно из вышеизложенного, у нас есть PostsActions
тип объединения, который ссылается на 2 действия, указанные выше. Позже мы будем использовать это в редукторе сообщений, чтобы убедиться, что мы работаем с правильными действиями.
Вы можете спросить, откуда мы получаем IPost []. Ну, есть отдельный файл, в котором мы создали интерфейс для сообщений, которые выглядят так.
export interface IPost { id: number; title: string; description: string; ... }
Поскольку получение сообщений из серверной части должно быть асинхронным действием, вот где Redux-Thunk решает нашу проблему. Однако использование TypeScript здесь будет немного сложнее.
export const fetchPostsActionCreator: ActionCreator< ThunkAction< Promise<IFetchPostsSuccessAction>, > > = () => { return async (dispatch: Dispatch) => { const startFetchPostsAction: IStartFetchPostsAction = { type: ‘StartFetchPosts’, }; dispatch(startFetchPostsAction); const posts = await fetch("API URL"); const fetchPostsSuccessAction: IFetchPostsSuccessAction = { posts, type: ‘FetchPostsSuccess’, }; return dispatch(fetchPostsSuccessAction); }; };
ActionCreator
- это общий тип из библиотеки Redux, который принимает тип, возвращаемый создателем действия. Создатель действия выше возвращает функцию, которая вернет IFetchPostsSuccessAction
. ThunkAction
впервые может показаться вам странным. Но это общий, который поставляется с библиотекой Redux-Thunk.
Использование TypeScript с редукторами
Давайте посмотрим, как будет выглядеть наш Reducer, когда мы реализуем TypeScript:
import { Reducer } from "redux"; import { IPostsState, PostsActions, } from "./PostsTypes"; const initialPostState: IPostsState = { posts: [], isLoading: false }; const postsReducer: Reducer<IPostsState, PostsActions> = ( state = initialPostsState, action, ) => { switch (action.type) { case ‘StartFetchPosts’: { return { …state, isloading: true, }; } case ‘FetchPostsSuccess’: { return { …state, posts: action.posts, isloading: false, }; } } return state; };
Также нам понадобится root reducer
import { combineReducers } from 'redux';
import PostsReducer from './reducers/PostsReducer'
const rootReducer = combineReducers<IAppState>({
Posts: postsReducer
});
export default rootReducer;
Мы используем общий тип Reducer
из библиотеки Redux, передавая наш тип состояния с типом объединения PostsActions
.
Аргумент action
в каждом случае оператора switch
имеет суженный тип до определенного действия, которое имеет отношение к конкретному случаю.
Использование TypeScript в магазине
Мы используем общий тип Store
из библиотеки Redux, передавая тип состояния нашего приложения, который в этом проекте равен IAppState
. Давайте внесем изменения в наш store.js
export function configureStore(): Store<IAppState> { const store = createStore(rootReducer, undefined, applyMiddleware(thunk)); return store; }
Использование TypeScript внутри компонента
Давайте посмотрим, как это будет выглядеть, когда мы реализуем TypeScript внутри нашего компонента.
import * as React from "react"; import { connect } from "react-redux"; import { RouteComponentProps } from "react-router-dom"; import { fetchPosts } from "./PostsActions"; import { IPost } from "./PostsData"; import PostsList from "./PostsList"; import { IAppState } from "./Store"; interface IProps extends RouteComponentProps { loading: boolean; posts: IPost[]; } class PostsPage extends React.Component<IProps> { public componentDidMount() { this.props.fetchPosts(); } public render() { ... return ( <div className="page-container"> <PostsList posts={this.props.posts} loading={this.props.loading} /> </div> ); } } const mapStateToProps = (store: IAppState) => { return { loading: store.posts.isLoading, posts: store.posts.posts }; }; const mapDispatchToProps = (dispatch: any) => { return { fetchPosts: () => dispatch(fetchPosts()) }; }; export default connect( mapStateToProps, mapDispatchToProps )(PostsPage);
Функция mapStateToProps
использует наш тип IAppState
, поэтому ссылки на состояние магазинов строго типизированы.
Подведение итогов
Интегрировать TypeScript в существующий проект React не так страшно, как многие думают. Конечно, лучше сгенерировать новый проект, когда вы только начинаете процесс разработки своего стартапа, и использовать TypeScript на самом раннем этапе, но в любом случае в обоих случаях теперь вы готовы принять этот вызов :)