使用 Typescript 将 React 和 Redux 提升到新的水平
序幕
如果你曾经使用过 Redux,你就会知道,我们编写 Redux 逻辑的方式以及它的工作机制很大程度上依赖于我们提前了解状态的结构。这种需求与优秀的 TypeScript 代码在构建输出 JavaScript 代码之前强制我们定义函数和变量的结构非常相似。
由于我近期会频繁使用 Redux,而且已经有一段时间没怎么用它了,所以我决定学习一下 Level Up Tutorials (LUT) 的《React and Redux For Everyone》课程,温习一下 Redux 的诸多概念。为了增加一些趣味,也因为我热爱 TypeScript,所以这次我决定用 TypeScript 编写这个教程应用。
这篇文章汇集了我的一些想法和经历的亮点。
一些示例代码
你可以在我的 GitHub上通过 git 标签查看课程代码以及我的每一步教程。我还创建了一个CodeSandbox,其中包含使用 Typescript 的最小设置react-redux
和连接组件。
你可以随意浏览它们,或者将它们作为你自己代码的灵感。我主要使用 Github 上的代码库来说明一些要点。
定义全局状态和根归约器
在我的仓库中,有两个 reducer 被合并combineReducers
,它们的状态定义如下:
movies
export interface IReduxMoviesState {
movies: IMovie[];
moviesLoaded: boolean;
moviesLoadedAt?: number;
movie?: IMovie;
movieLoaded: boolean;
}
toggle
export interface IReduxMessageState {
messageVisibility: boolean;
}
通过我们的 reducer 返回每个状态,我们可以定义全局应用程序状态,如下所示:
const rootReducer = combineReducers({
toggle,
movies
});
export type AppState = ReturnType<typeof rootReducer>;
这使得它AppState
看起来像:
type AppState = {
toggle: IReduxMessageState;
movies: IReduxMoviesState;
};
这很棒,因为无论在哪里使用我们的 redux 状态,我们都确切地知道它是什么样子,以及在连接组件时我们可以从中引用什么。
定义动作创建者和动作类型常量
在 Redux 中,将操作类型定义为常量是一种常见的做法。因为我们使用的是 TypeScript,所以我们可以使用枚举和扩展接口来使代码更具描述性。在我的代码库中,我有以下操作类型的枚举:
export enum EReduxActionTypes {
GET_MOVIE = 'GET_MOVIE',
GET_MOVIES = 'GET_MOVIES',
RESET_MOVIE = 'RESET_MOVIE',
TOGGLE_MESSAGE = 'TOGGLE_MESSAGE'
}
如果你熟悉 Typescript,你会发现我给枚举定义了值。这是为了避免枚举键被赋值为数值,因为这可能会降低代码的弹性。无论如何,这都会让我们定义 action creators 更容易一些。
我根据具有更通用值的接口定义了操作type
,它非常简单,但具有很强的可扩展性:
export interface IReduxBaseAction {
type: EReduxActionTypes;
}
例如,在电影 Reducer 的情况下,可以分派几个不同的动作:
export interface IReduxGetMoviesAction extends IReduxBaseAction {
type: EReduxActionTypes.GET_MOVIES;
data: IMovie[];
}
export interface IReduxGetMovieAction extends IReduxBaseAction {
type: EReduxActionTypes.GET_MOVIE;
data: IMovie;
}
export interface IReduxResetMovieAction extends IReduxBaseAction {
type: EReduxActionTypes.RESET_MOVIE;
}
与 Typescript 中的许多事物一样,您不需要知道数据的值是如何定义的,在这种情况下,您只需知道每个操作都将包含data
我们操作属性的正确类型的对象或数组。
通过将这些类型聚合为联合类型,我可以像下面这样编写电影减速器:
type TMoviesReducerActions = IReduxGetMoviesAction | IReduxGetMovieAction | IReduxResetMovieAction;
export default function(state: IReduxMoviesState = initialState, action: TMoviesReducerActions) {
switch (action.type) {
case EReduxActionTypes.GET_MOVIES:
return { ...state, movies: action.data, moviesLoaded: true, moviesLoadedAt: Date.now() };
case EReduxActionTypes.GET_MOVIE:
return { ...state, movie: action.data, movieLoaded: true };
case EReduxActionTypes.RESET_MOVIE:
return { ...state, movie: undefined, movieLoaded: false };
default:
return state;
}
}
这个 Reducer 是这个 TS 和 Redux 实现中我最喜欢的部分之一。
EReduxActionTypes
因为我对每个动作使用了不同的值。当我进入action.data
不同的case
时,Typescript 已经知道数据是正确的类型,即Imovie
和(电影数组)IReduxGetMovieAction
。IMovie[]
IReduxGetMoviesAction
这是一件非常强大的事情。
在我的教程应用程序中,减速器相当简单,但我们已经可以看到,扩展它并不是什么大问题,也不会真正增加我们商店的复杂性。
如果我们考虑到 VS Code 为 Typescript 提供的出色的开发人员体验,这一点尤其正确。
连接组件并使用我们的商店状态
为了将我们的movies
状态与MoviesList
组件连接起来,使用的代码如下:
const mapStateToProps = (state: AppState) => ({
movies: state.movies.movies,
isLoaded: state.movies.moviesLoaded,
moviesLoadedAt: state.movies.moviesLoadedAt
});
const mapDispatchToProps = (dispatch: Dispatch<AnyAction>) =>
bindActionCreators(
{
getMovies
},
dispatch
);
export default connect(
mapStateToProps,
mapDispatchToProps
)(MoviesList);
这里有相当多的代码和值的重新赋值。通常这可能会导致一些混淆,关于哪些 props 可用于我们的MoviesList
组件,但 Typescript 会确保不会发生这种情况,它允许我们解析mapStateToProps
和的类型定义mapDispatchToProps
并在创建组件时使用它:
class MoviesList extends PureComponent<ReturnType<typeof mapStateToProps> & ReturnType<typeof mapDispatchToProps>, {}> {
// Code for the component goes here
}
MoviesList
我们甚至可以通过创建如下 props 类型来稍微简化事情:
type TMoviesListProps = ReturnType<typeof mapStateToProps> & ReturnType<typeof mapDispatchToProps>;
class MoviesList extends PureComponent<TMoviesListProps, {}> {
// Code for the component goes here
}
现在,如果我们尝试从组件内部引用任何内容,我们将能够完全看到和this.props
提供给我们的所有属性。mapStateToProps
mapDispatchToProps
结论
尽管使用 Redux 管理状态并遵循其标准实践可能会导致我们将逻辑分散到多个文件中,并且/或者添加大量的样板代码。通过使用 Typescript,我们可以大大提高代码的可读性,并且对于那些不太了解复杂应用程序的来龙去脉、每个部分负责什么以及他们期望从其他组件接收什么的人来说,它可能会更容易理解。
这个教程应用可能不是最复杂的,我可能也没有最细致地使用 Typescript。但我仍然认为它凸显了 Typescript 的一些强大功能,以及为什么最近越来越多的人开始研究它。
您如何看待 Typescript?它如何改变我们开发者在创建和扩展应用程序时的体验?欢迎在下方评论或通过社交媒体联系我,详情请访问我的网站:leomeloxp.dev。
最后一点。在编写此应用时,我尽量使代码与 LUT 的“人人皆可使用的 React 和 Redux”课程中的原始代码保持一致。如果您想了解更多关于该课程或 Level Up 教程的信息,欢迎访问他们的网站。
这篇文章不是由 Level Up Tutorials 赞助的,我只是真的很喜欢他们的内容。
文章来源:https://dev.to/leomeloxp/take-react-and-redux-to-the-next-level-with-typescript-1m84