We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
最近看到了一篇非常好的文章: redux 文档到底说了什么(上), 加之最近项目可能需要用到 Redux + Typescript. 所以仔细拜读了这篇博客, 受益匪浅. 看完后自己尝试又重新根据自己的理解实现了一遍, 部分代码和思路参考了原文, 所以首先还是非常感谢作者的这篇好文.
由于这篇文章比较注重于讲原生 Redux + TypeScript 的写法, 且不涉及到使用 Redux Toolkit 这个库, 如果对这个库使用有兴趣的可以同样可以参考作者的另一篇文章redux 文档到底说了什么(下)
另有些不重要的细节例如 css 等就不做深究了
在写的过程中其实发现还是有部分的写法以及问题文档里已经提到过了, 所以一般我会选择按照文档的套路来, 具体关于 React + Redux + TypeScript 的链接如下:
想跳过文章直接看代码的: 完整代码
最后的效果:
使用了 mockapi 这个在线工具, 非常方便来模拟增删改查接口并且是免费的. 返回的响应格式如下:
我自己的 API 端点为: https://5d2d9b4343c343001498d272.mockapi.io/api/v1/todos
用到的库有 React + Redux + Redux Thunk + TypeScript + Antd
目录结构为:
├── package.json ├── public │ └── index.html └── src ├── README.md ├── api │ └── index.ts ├── components │ ├── App.tsx │ ├── TodoApp.tsx │ └── TodoItem.tsx ├── index.tsx ├── store │ ├── filter │ │ ├── actionTypes.ts │ │ ├── actions.ts │ │ ├── constants.ts │ │ ├── reducer.ts │ │ └── types.ts │ ├── index.ts # store 的入口 │ ├── loading │ │ ├── actionTypes.ts │ │ ├── actions.ts │ │ ├── constants.ts │ │ ├── reducer.ts │ │ ├── selectors.ts │ │ └── types.ts │ └── todo │ ├── actionTypes.ts # action 的类型 │ ├── actions.ts # 所有的 action, 包括标准的 actionCreator 和 thunk 类型的 action │ ├── constants.ts # 常量, 主要存放 action type 的类型和值 │ ├── reducer.ts # todo 的 reducer │ ├── selectors.ts # 选择器 │ └── types.ts # 基本公用的类型 └── style.css
可以看到其实组件部分没有分的很细, store 也就是 redux 部分分的比较细, 一共是有三个 slice, 每个 slice 都有大致相同的结构, 真实大项目可能不同, 这里只是为了演示.
store
redux
slice
可以看到要实现的 TodoApp 有如下的操作(action):
action
先从最简单的 loading 这个 slice 开始. 由于这个项目是需要和后端交互的, 交互过程需要时间, 因此可以在发送请求等待响应的过程中显示 loading 组件, 即这个时候的 loading 的状态应该是 true, 在得到响应后, loading 状态为 false, 相关的 loading 组件也不再展示
loading
true
false
编写一下 loading 对应的状态, 这里的 status 表示当前 loading 的状态, tip 为可选, 在 loading 为 true 的时候显示加载的具体提示信息
status
tip
// store/loading/types.ts export type LoadingState = { status: boolean; tip?: string; };
action 部分也比较简单, 正常就是 setLoading 和 unsetLoading 两个状态. 如果是在写 JavaScript 直接写就行了, 但是在写 TypeScript 的时候需要先编写 action 的类型(actionTypes), 由于发出的 action 往往只有一种. 在这个 action 进入 reducer 的时候, reducer 其实是需要知道这个 action 是哪种类型的, 有具体的类型也有助于后面编写 reducer 有更好的类型提示
setLoading
unsetLoading
actionTypes
reducer
// store/loading/actionTypes.ts export type SetLoading = { type: 'SET_LOADING'; payload: string; }; export type UnsetLoading = { type: 'UNSET_LOADING'; }; export type LoadingAction = SetLoading | UnsetLoading;
为了更好的组织代码, 这里将 type 字符串单独放到一个文件里, 导出具体的类型和值.
type
// store/loading/constants.ts export const SET_LOADING = "SET_LOADING"; export type SET_LOADING = typeof SET_LOADING; export const UNSET_LOADING = "UNSET_LOADING"; export type UNSET_LOADING = typeof UNSET_LOADING;
这里有一个很有意思的地方, TypeScript 中其实有些类型既可以表示值也可以表示变量, 具体是有一个术语的(但我忘了这个术语叫啥...). 仔细想想, 如果声明一个类, 这个类既不就是既可以当类型又可以当一个类使用么?
class Person { } const p: Person = new Person()
现在 actionTypes.ts 里需要改一下:
actionTypes.ts
// store/loading/constants.ts import { SET_LOADING, UNSET_LOADING } from "./constants"; export type SetLoading = { type: SET_LOADING; payload: string; }; export type UnsetLoading = { type: UNSET_LOADING; }; export type LoadingAction = SetLoading | UnsetLoading;
redux 有导出一个 ActionCreator<A> 的类型, 所以一开始我写出来是这样的:
ActionCreator<A>
// store/loading/actions.ts import { SetLoading, UnsetLoading } from "./actionTypes"; import { SET_LOADING, UNSET_LOADING } from "./constants"; import { ActionCreator } from "redux"; export const setLoading: ActionCreator<SetLoading> = (tip: string) => { return { type: SET_LOADING, payload: tip }; }; export const unsetLoading: ActionCreator<UnsetLoading> = () => { return { type: UNSET_LOADING }; };
看上去好像没问题, 实际上是有很大问题的. 问题的根本在于 ActionCreator<A> 这个类型实际上非常不精确, 它是没有办法正确推断返回的 action 的 key 类型的, 举个例子, 假设我改成这样写:
key
// store/loading/actions.ts export const setLoading: ActionCreator<SetLoading> = (tip: string, a: number) => { return { type: SET_LOADING, payload: tip, a: a }; }; // ...
编译是通过的, 但是语义上完全是错误的. 这里给一下我当时搜到的相关 issue: [bug: typescript] ActionCreator lose it's arguments type, 这个 issue 到今天为止还是开着的...
我们可以看一下源码(虽然最新的源码和 issue 里引用的不同有更新, 但是最新的 redux 包里的貌似还是使用的旧的, 这里两种定义方式都列出)
// github 源代码 master 分支上最新的, 但在 4.0.5 发布的包里并未包含这种写法 export interface ActionCreator<A, P extends any[] = any[]> { (...args: P): A } // "旧的", 也为当前最新的 4.0.5 包里的类型定义 export interface ActionCreator<A> { (...args: any[]): A }
解决办法也是有的, 而且非常简单, 官网包括该 issue 下的一个评论 都给出了写法, 简单讲就是定义好 actionCreator 返回的类型, 其余包括参数等让 TypeScript 自行推断, 具体写法如下:
actionCreator
TypeScript
// store/loading/actions.ts import { SetLoading, UnsetLoading } from "./actionTypes"; import { SET_LOADING, UNSET_LOADING } from "./constants"; export const setLoading = (tip: string): SetLoading => { return { type: SET_LOADING, payload: tip }; }; export const unsetLoading = (): UnsetLoading => { return { type: UNSET_LOADING }; };
这里 redux 有提供一个工具类型 Reducer<S, A>, 我用下来没有什么坑, 不过用官网的写法也是没问题的
Reducer<S, A>
// store/loading/reducer.ts import { Reducer } from "redux"; import { LoadingAction } from "./actionTypes"; import { SET_LOADING, UNSET_LOADING } from "./constants"; import { LoadingState } from "./types"; const intialState: LoadingState = { status: false }; export const loadingReducer: Reducer<Readonly<LoadingState>, LoadingAction> = ( state = intialState, action ) => { switch (action.type) { case SET_LOADING: return { status: true, tip: action.payload }; case UNSET_LOADING: return { status: false }; default: return state; } };
这里稍微提一下 reducer, reducer 返回的就是当前 store 里的状态, 这个状态怎么涉及其实挺有讲究. 原文章 包括 Redux 官网都提到一个点就是可以对状态进行Normalize. 这个对于性能优化是挺有帮助的. 有兴趣可以了解
另一个点是, reducer 最后的 default: return state 在没有特殊情况下都应该这样写. 我之前就有昏了头写成 default: return initialState, 然后就因为这个 bug 我找了一下午...实际上每一个被 dispatch 的 action 都会在 reducer 里走一遍, 当所有 type 都匹配不到就会走最后一个 default 分支, 其状态也就不变维持之前的状态
default: return state
default: return initialState
dispatch
default
最后部分是 selectors 部分, 如果用 hooks 的话会需要用到 useSelector() 来从 store 中拿对应的数据, 这部分我个人理解放在组件里写或者抽成一个单独文件其实都可以, 这里就放在一个单独文件里了
selectors
useSelector()
// store/loading/selectors.ts import { RootState } from "../index"; export const selectLoading = (state: RootState) => { return state.loading; };
这里其实有点跳跃了, RootState 其实就是整个 redux store 里的所有状态的合集. 具体看下面的章节的实现.
RootState
另外, 关于 selector 或者说衍生数据, redux 官网专门开了一章: Computing Derived Data来写怎么计算获取衍生状态数据, 涉及到性能优化等方面非常多, 这里不做深究.
selector
前面有提到需要定义 RootState 状态, 但之前需要先定义一下总的 reducer, 这里我们用 combineReducer() 来集成所有细分的 reducer. 而 RootState 其实就是 combineReducer() 最后返回的 rootReducer() 的返回值(有兴趣可以去看一下 combineReducer() 的源码, 很有意思, 面试可能会让你手写一个!)
combineReducer()
rootReducer()
// store/index.ts const rootReducer = combineReducers({ todos: todoReducer, // filter: filterReducer, // loading: loadingReducer }); export type RootState = ReturnType<typeof rootReducer>;
至此关于 Loading 的 store 部分就写完了, 后续还会写两篇文章完成剩余的 TodoApp 部分:
Loading
TodoApp
todo
filter
Redux Thunk
React
Hooks
React Redux
The text was updated successfully, but these errors were encountered:
No branches or pull requests
前言
最近看到了一篇非常好的文章: redux 文档到底说了什么(上), 加之最近项目可能需要用到 Redux + Typescript. 所以仔细拜读了这篇博客, 受益匪浅. 看完后自己尝试又重新根据自己的理解实现了一遍, 部分代码和思路参考了原文, 所以首先还是非常感谢作者的这篇好文.
由于这篇文章比较注重于讲原生 Redux + TypeScript 的写法, 且不涉及到使用 Redux Toolkit 这个库, 如果对这个库使用有兴趣的可以同样可以参考作者的另一篇文章redux 文档到底说了什么(下)
另有些不重要的细节例如 css 等就不做深究了
在写的过程中其实发现还是有部分的写法以及问题文档里已经提到过了, 所以一般我会选择按照文档的套路来, 具体关于 React + Redux + TypeScript 的链接如下:
想跳过文章直接看代码的: 完整代码
最后的效果:
配置与实现思路
后端
使用了 mockapi 这个在线工具, 非常方便来模拟增删改查接口并且是免费的. 返回的响应格式如下:
我自己的 API 端点为: https://5d2d9b4343c343001498d272.mockapi.io/api/v1/todos
前端
用到的库有 React + Redux + Redux Thunk + TypeScript + Antd
目录结构为:
可以看到其实组件部分没有分的很细,
store
也就是redux
部分分的比较细, 一共是有三个slice
, 每个slice
都有大致相同的结构, 真实大项目可能不同, 这里只是为了演示.思路
可以看到要实现的 TodoApp 有如下的操作(
action
):Loading
先从最简单的
loading
这个 slice 开始. 由于这个项目是需要和后端交互的, 交互过程需要时间, 因此可以在发送请求等待响应的过程中显示loading
组件, 即这个时候的loading
的状态应该是true
, 在得到响应后,loading
状态为false
, 相关的loading
组件也不再展示types
编写一下
loading
对应的状态, 这里的status
表示当前loading
的状态,tip
为可选, 在loading
为true
的时候显示加载的具体提示信息actions
actionTypes
action
部分也比较简单, 正常就是setLoading
和unsetLoading
两个状态. 如果是在写 JavaScript 直接写就行了, 但是在写 TypeScript 的时候需要先编写action
的类型(actionTypes
), 由于发出的action
往往只有一种. 在这个action
进入reducer
的时候,reducer
其实是需要知道这个action
是哪种类型的, 有具体的类型也有助于后面编写reducer
有更好的类型提示为了更好的组织代码, 这里将
type
字符串单独放到一个文件里, 导出具体的类型和值.这里有一个很有意思的地方, TypeScript 中其实有些类型既可以表示值也可以表示变量, 具体是有一个术语的(但我忘了这个术语叫啥...). 仔细想想, 如果声明一个类, 这个类既不就是既可以当类型又可以当一个类使用么?
现在
actionTypes.ts
里需要改一下:actionCreators
redux
有导出一个ActionCreator<A>
的类型, 所以一开始我写出来是这样的:看上去好像没问题, 实际上是有很大问题的. 问题的根本在于
ActionCreator<A>
这个类型实际上非常不精确, 它是没有办法正确推断返回的action
的key
类型的, 举个例子, 假设我改成这样写:编译是通过的, 但是语义上完全是错误的. 这里给一下我当时搜到的相关 issue: [bug: typescript] ActionCreator lose it's arguments type, 这个 issue 到今天为止还是开着的...
我们可以看一下源码(虽然最新的源码和 issue 里引用的不同有更新, 但是最新的 redux 包里的貌似还是使用的旧的, 这里两种定义方式都列出)
解决办法也是有的, 而且非常简单, 官网包括该 issue 下的一个评论 都给出了写法, 简单讲就是定义好
actionCreator
返回的类型, 其余包括参数等让TypeScript
自行推断, 具体写法如下:reducer
这里 redux 有提供一个工具类型
Reducer<S, A>
, 我用下来没有什么坑, 不过用官网的写法也是没问题的这里稍微提一下
reducer
,reducer
返回的就是当前store
里的状态, 这个状态怎么涉及其实挺有讲究. 原文章 包括 Redux 官网都提到一个点就是可以对状态进行Normalize. 这个对于性能优化是挺有帮助的. 有兴趣可以了解另一个点是,
reducer
最后的default: return state
在没有特殊情况下都应该这样写. 我之前就有昏了头写成default: return initialState
, 然后就因为这个 bug 我找了一下午...实际上每一个被dispatch
的action
都会在reducer
里走一遍, 当所有type
都匹配不到就会走最后一个default
分支, 其状态也就不变维持之前的状态selectors
最后部分是
selectors
部分, 如果用 hooks 的话会需要用到useSelector()
来从store
中拿对应的数据, 这部分我个人理解放在组件里写或者抽成一个单独文件其实都可以, 这里就放在一个单独文件里了这里其实有点跳跃了,
RootState
其实就是整个redux
store
里的所有状态的合集. 具体看下面的章节的实现.另外, 关于
selector
或者说衍生数据, redux 官网专门开了一章: Computing Derived Data来写怎么计算获取衍生状态数据, 涉及到性能优化等方面非常多, 这里不做深究.Store
前面有提到需要定义
RootState
状态, 但之前需要先定义一下总的reducer
, 这里我们用combineReducer()
来集成所有细分的reducer
. 而RootState
其实就是combineReducer()
最后返回的rootReducer()
的返回值(有兴趣可以去看一下combineReducer()
的源码, 很有意思, 面试可能会让你手写一个!)总结
至此关于
Loading
的store
部分就写完了, 后续还会写两篇文章完成剩余的TodoApp
部分:todo
和filter
部分, 涉及到Redux Thunk
和TypeScript
的结合使用React
,Hooks
和TypeScript
以及React Redux
里相关Hooks
的使用参考
The text was updated successfully, but these errors were encountered: