mapStateToProps && mapDispatchToProps
如果组件需要从 store 中获取一些数据或者方法经常会这么写:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import { TRepo } from "store/repos/types";
interface RepoProps { repos: TRepo[]; username: string; push: Function; fetchRepos: Function; } class Repos extends React.Component<RepoProps, {}> {}
const mapStateToProps = ({ repos, router }: ApplicationState) => ({ repos: repos.repos, username: queryString.parse(router.location.search).username });
const mapDispatchToProps = (dispatch, getState) => { return { push: url => dispatch(push(url)), fetchRepos: value => dispatch(reposActions.fetchReposByUser(value)) }; };
|
上面的这种写法有一个问题,需要人工维护 mapStateToProps 和 mapDisPatchToProps 与 RepoProps 的统一,如果 mapStateToProps 返回更多数据,RepoProps 就需要跟着修改。这里可以利用 ReturnType 减少不必要的重复
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| export type TConnectType< S extends (...args: any[]) => any, D extends (...args: any[]) => any > = ReturnType<S> & ReturnType<D>;
import { ApplicationState, TConnectType } from "store";
export interface ReposProps {}
type AllProps = ReposProps & TConnectType<typeof mapStateToProps, typeof mapDispatchToProps>;
class Repos extends React.Component<AllProps, any> {}
|
typesafe
让 Redux 实现 typesafe,可以使用tyepsafe-actions
具体使用这里就不介绍了。
简化 switch case
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| switch (actin.type) { case getType(globalActions.getVersionSuccess): return { ...state, version: action.payload };
case getType(globalActions.toggleLogin): return { ...state, showLogin: action.payload };
case getType(globalActions.getUserLoginWaySuccess): return { ...state, loginWay: action.payload }; }
|
以上 case 很常见:
但是却很繁琐,逻辑重复。我们来封装一个函数:
1 2 3 4 5 6 7 8 9 10 11 12 13
| function handleInit<T extends {}, K extends keyof T>( state: T, stateKey: K, action ): T { return { ...state, [stateKey]: action.payload }; } export function handleAll<T>(state: T, action, handle = handleInit): T { if (action.stateKey) { return handle(state, action.stateKey, action); } return state; }
|
使用如下:
1 2 3 4 5 6 7 8 9 10
| dispatch({ type: "GET_VERSION", stateKey: "version" });
switch (actin.type) { default: return handleAll(state, action); }
|
action
使用 typesafe-actions 来创建一个 action 如下:
1 2 3 4 5 6 7
|
export const getVersionSuccess = createStandardAction( GlobalTypes.global_VERSION.success )<string>();
|
如果想使用 handle 来简化 case,需要在 action 中添加 stateKey:
1 2 3 4 5
| { type: 'any_type', payload: 'v1.2', stateKey: 'version' }
|
我们封装下 createStandardAction 来支持 stateKey:
1 2 3 4 5 6 7 8 9 10 11 12
|
export function createStandardAction2<T extends string>(type: T) { return function inner<P, SAll>(stateKey: keyof SAll) { return createStandardAction(type).map((payload: P) => ({ payload, stateKey })); }; }
|
现在我们来重写:
1 2 3 4 5 6 7
| type GlobalState = { version: string; other: string; }; export const getVersionSuccess = createStandardAction2( GlobalTypes.global_VERSION.success )<string, GlobalState>("versin");
|
简化 action type 创建
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
| export enum GlobalTypes { IS_CLUSTER = "global/IS_CLUSTER", GET_USER_INFO = "global/GET_USER_INFO", GET_USER_INFO_SUCCESS = "global/GET_USER_INFO_SUCCESS", GET_USER_INFO_ERROR = "global/GET_USER_INFO_ERROR", LOGOUT = "global/LOGOUT", USER_INSTALL_WAY = "global/USER_INSTALL_WAY", USER_INSTALL_WAY_SUCCESS = "global/USER_INSTALL_WAY_SUCCESS", USER_INSTALL_WAY_ERROR = "global/USER_INSTALL_WAY_ERROR", VERSION = "global/VERSION", VERSION_SUCCESS = "global/VERSION_SUCCESS", VERSION_ERROR = "global/VERSION_ERROR", LOGIN_WAY = "global/LOGIN_WAY", LOGIN_WAY_SUCCESS = "global/LOGIN_WAY_SUCCESS", LOGIN_WAY_ERROR = "global/LOGIN_WAY_ERROR", TOGGLE_LOGIN = "global/TOGGLE_LOGIN", GET_INSIGHT_URL = "global/GET_INSIGHT_URL", GET_INSIGHT_URL_SUCCESS = "global/GET_INSIGHT_URL_SUCCESS", GET_INSIGHT_URL_ERROR = "global/GET_INSIGHT_URL_ERROR", GET_URLS = "global/GET_URLS", GET_URLS_SUCCESS = "global/GET_URLS_SUCCESS", GET_URLS_ERROR = "global/GET_URLS_ERROR", ADMIN_USER = "global/ADMIN_USER" }
|
简化后:
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
| enum BasicTypes { global_IS_CLUSTER, global_TOGGLE_LOGIN, global_ADMIN_USER }
enum FetchTypes { global_GET_USER_INFO, global_LOGOUT, global_USER_INSTALL_WAY, global_VERSION, global_LOGIN_WAY, global_GET_INSIGHT_URL, global_GET_URLS }
export type TFetchTypes = typeof FetchTypes;
export const GlobalTypes = composeTypes({ BasicTypes, FetchTypes });
|
简化异步 action
一个异步 action 通常需要创建三个不同的 action
- request
- success
- failure ( error )
typesafe-actions 已经为我们封装了创建异步 action 集合的方法:
1 2 3 4 5 6 7
| const actions = createAsyncAction( requestType, successType, failureType, cancelType? )<TRequestPayload, TSuccessPayload, TFailurePayload, TCancelPayload?>()
actions.request(TRequestPayload) actions.success(TSuccessPayload) actions.failure(TFailurePayload)
|
简化前:
1 2 3 4 5 6 7
| export const getUrls = createAction(GlobalTypes.global_GET_URLS.request); export const getUrlsSuccess = createStandardAction( GlobalTypes.global_GET_URLS.success )<TUrls>(); export const getUrlsFailure = createStandardAction( GlobalTypes.global_GET_URLS.error )<string>();
|
简化后:
1 2 3 4 5
| const actions = createAsyncAction( GlobalTypes.global_GET_URLS.request, GlobalTypes.global_GET_URLS.success, GlobalTypes.global_GET_URLS.error )<undefined, TUrls, string>();
|
可以看到,创建异步 action 的步骤已经极大简化。但是,上边的 action type 还是略重复,为了去除重复我们包装一下 createAsyncAction:
1 2 3 4 5 6 7 8 9 10 11 12
| type TAsyncTypes<K extends string> = { request: "loading"; success: K; error: "error"; };
export function createAsyncAction2<T extends string>(types: TAsyncTypes<T>) { const { request, success, error } = types; return function inner<T1, T2, T3 = Error, T4 = string>() { return createAsyncAction(request, success, error)<T1, T2, T3, T4>(); }; }
|
使用 createAsyncAction2 之后,生成异步 actions 集合方法就非常简单了:
1 2 3 4 5
| const actions = createAsyncAction(GlobalTypes.global_GET_URLS)< undefined, TUrls, string >();
|
简化 loading
loading 的处理对于 redux 来说比较繁琐,这里我单独写了一篇文章:
总结
使用 Redux 是比较繁琐的,但是可以通过进一步封装来简化代码,加上 Typescript 加持,编写高质量代码更加容易。