Redux 中处理 loading 是比较繁琐的,经常会出现重复代码:
1 2 3 4 5 6 7 8 9 10 11 12
| switch (action.type) { case "getInfo": return { ...state, infoLoading: true }; case "getInfoSuccess": return { ...state, infoLoading: false }; }
|
重构 1
我们可以考虑建一个单独的 loading reducer,把所有有关 loading 的字段放到一块处理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| export const types = { GLOBAL_LOADING: "globalLoading/GLOBAL_LOADING" };
const initialState = {};
function globalLoading(state = initialState, action) { const { name, value } = action; switch (action.type) { case types.GLOBAL_LOADING: if (Array.isArray(name)) { const res = name.reduce((ob, name) => { ob[name] = false; return ob; }, {}); return { ...state, ...res }; } return { ...state, [name]: value };
default: return state; } }
export default globalLoading;
|
调用如下:
1 2 3 4 5 6 7 8 9 10
| setLoading(["userInfo", "list"], true);
setLoading(["userInfo", "list"], false)
functoin mapStateToProps(state){ return { userInfoLoading: state.globalLoading.userInfo, listLoading: state.globalLoading.list } }
|
这个方法虽然集合了所有的 loading,但是缺点很多:
- 需要手动调用开始和结束 loading 状态。
- ts 无法检测到 loading 关键字,如 globalLoading 不知道自己有 userInfo,除非你手动加进 globalLoading 类型中。
重构 2
同样将 loading 提取到一个 reducer 来处理,不同的是这次使用一定的规则来编写 action.type:
- 带有 _{typeName}_REQUEST_ 的 type 表示异步请求开始,例如 GET_USER_INFO_REQUEST
- 带有 _{typeName}_SUCCESS_ 的 type 表示异步请求成功,例如 GET_USER_INFO_SUCCESS
- 带有 _{typeName}_ERROR_ 的 type 表示异步请求开始,例如 GET_USER_INFO_ERROR
通过正则检测 type,可以确定 type 的名字和状态:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
import { GlobalLoadingState } from "./types";
const initialState: GlobalLoadingState = {};
const reducer: Reducer<GlobalLoadingState> = (state = initialState, action) => { const { type } = action; const matches = /(.*)_(REQUEST|SUCCESS|ERROR)/.exec(type);
if (!matches) return state;
const [, requestName, requestState] = matches; return { ...state, [requestName]: requestState === "REQUEST" }; };
|
使用如下:
1 2 3 4
| dispatch("getUser_REQUEST");
dispatch("getUser_SUCCESS");
|
一个 store 中其实包含两种 action,一种普通的 action,另一种需要异步处理的 action:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
|
enum BasicTypes { global_ADMIN_USER }
enum FetchTypes { global_GET_USER_INFO }
export type TFetchTypes = typeof FetchTypes;
export const GlobalTypes = composeTypes({ BasicTypes, FetchTypes });
|
上边拆分了普通 action 和需要异步的 action,我们把这些异步的 action 类型导入到全局的 loading 类型中:
1 2 3 4 5 6 7 8 9 10
|
import { TFetchTypes } from "store/global/types";
type GlobalLoadingKeys = keyof TFetchTypes;
export type GlobalLoadingState = Partial< { [key in GlobalLoadingKeys]: boolean } >;
|
这样,ts 就会提示 loading 属性了:
1 2 3
| const mapStateToProps = (state: ApplicationState) => ({ userLoading: state.globalLoading.global_GET_USER_INFO });
|