npm:
npm i -S iron-redux
yarn:
yarn add iron-redux
- 巧妙利用 Typescript 的类型推导能力,不需要额外定义任何类型,可以使 redux 整体流程类型完美。
- 让 Redux 代码极其精简!去除任何冗余的、形式化的代码!参看 example。
- 自动推导出整个项目的 Redux 全局状态树的类型。
- 让 reducer 每个 case 都能获取不同的 action 类型,可在 vscode 中参看 example;
- vscode IDE 插件支持。
- 非常轻量级!源码只有 300 行!零依赖!
简单的 action type 可以增加至 enum BasicTypes。复杂的 action type 可以增加到 enum FetchTypes。所有的 action type 可以在 Types 中获取:
enum BasicTypes {
changeId
}
enum FetchTypes {
fetchId,
// 这样拿到的 Types.loadData 相当于没有 Error Type。因此 View中可以 .catch
loadData = NO_ERROR_TYPES
}
const prefix = 'test/';
const Types = composeTypes({
prefix,
BasicTypes,
FetchTypes
});
// Types.changeId === 'test/changeId';
// Types.fetchId.success === 'test/changeId_SUCCESS';
// Types.fetchId.loading === 'test/changeId_LOADING';
// Types.fetchId.error === 'test/changeId_ERROR';
增加简单的 actionCreator,可以调用 createAction(actionType)<payload 类型>(payload 转换函数)
const actions = {
changeId: createAction(Types.changeId)<number>()
};
// 调用
// actions.changeId(3); 返回: { type: 'test/chagneId', payload: 3 };
const actions = {
changeId: createAction(Types.changeId)((id: number) => {
return {
id,
};
}),
};
// 调用
// action.changeId(3); 返回: { type: 'test/changeId', payload: { id: 3} }
当然你也可以不用 createAction,自己手写 action。但是记得一定要完善类型不要使用 any。如:
const actions = {
changeId(id: number) {
return { type: Types.changeId, payload: id };
},
};
thunkAction 的返回类型请使用 ThunkAction 来声明。dispatch 已经拥有类型。getState 请使用已经定义好的 GetState 类型。
const actions = {
putUdf: some action,
updateFunc(params: Params): ThunkAction {
return async (dispatch, getState: GetState) => {
// 此处可以拿到 result 的类型为 putUdf 这个接口的返回类型
const result = await dispatch(actions.putUdf(params));
};
},
}
ThunkAction 返回 Promise:
const actions = {
updateFunc(params: Params): ThunkAction<Response> {
return async (dispatch, getState: GetState) => {
// 此处可以拿到 result 的类型为 putUdf 这个接口的返回类型
const result = await dispatch(actions.putUdf(params));
// const state = getState();
// 返回这个Promise,供view中使用
return dispatch(actions.loadDetailFuncInfo(params.udfId));
};
}
};
// view 中
this.props.updateFunc.then(data => {});
- 1、复杂的属性可以尽量写些注释,方便调用的时候可以辨识。
- 2、接口返回类型如果要处理 loading、error。请使用 AsyncTuple。使用 API 中的 init 一方面提供了类型,一方面提供了接口的初始值,该初始值可以防止复杂对象后端返回 undefined
- 3、InitialState 里不要有各种 loading、error 字段,代码阅读者无法区分这是哪个请求的 loading 或者 error。建议都使用 AsyncTuple 来做。
class InitialState {
isDialogVisible = false;
detailFuncInfo = new AsyncTuple(API.ideFunction.getDetailById.init);
id = 0;
}
存储某个数据及其 loading error 状态的类。包含静态方法 handleLoading
, handleError
, handleSuccess
。
class InitialState {
data = new AsyncTuple(someResponse);
}
使用静态方法
case Types.loadData.loading: {
return AsyncTuple.handleLoading("data", state);
}
case Types.loadData.success: {
return AsyncTuple.handleSuccess("data", state, action);
}
case Types.loadData.error: {
return AsyncTuple.handleError("data", state, action);
}
使用数据:
const state: InitialState;
state.data.loading
stata.data.data
state.data.error
reducer 没有什么好说的。根据自己的需求写就好了。AsyncTuple 的 case 可以使用如下方法调用。
case Types.loadUdfFuncInfo.loading: {
return AsyncTuple.handleLoading("udfFuncInfo", state);
}
case Types.loadUdfFuncInfo.success: {
return AsyncTuple.handleSuccess("udfFuncInfo", state, action);
}
case Types.loadUdfFuncInfo.error: {
return AsyncTuple.handleError("udfFuncInfo", state, action);
}
为了避免在 reducer 的各种 case 中处理冗余而啰嗦的 AsyncTuple 逻辑(包括 handleError、handleLoading、handleSuccess)。可以在 default 中使用 handleAll 方法。
const actions = {
// 注意在action处要指定字段名: listData
fetch: ApI.xx.xx.createFetchAction(Types.fetch, 'listData'),
};
class InitialState {
// 注意该对应字段默认应该是 AsyncTuple。
listData = new AsyncTuple();
}
/**
* reducer
*/
function reducer(
state = new InitialState(),
action: ActionType<typeof actions>
): InitialState {
switch (action.type) {
// 这里可以避免写各种 AsyncTuple。
default: {
return AsyncTuple.handleAll(prefix, state, action);
}
}
}
其中 AsyncTuple.handleAll 是可以由用户来自定义的:
// 自定义处理函数
function process<K extends keyof T, T extends Object>(
stateKey: K,
state: T,
action,
fetchType: "loading" | "success" | "error"
): T {
if (fetchType === "loading") {
return AsyncTuple.handleLoading(stateKey, state);
} else if (fetchType === "success") {
return AsyncTuple.handleSuccess(stateKey, state, action);
} else if (fetchType === "error") {
return AsyncTuple.handleError(stateKey, state, action);
}
return state;
}
/**
* reducer
*/
function reducer(
state = new InitialState(),
action: ActionType<typeof actions>
): InitialState {
switch (action.type) {
// 这里可以避免写各种 AsyncTuple。
default: {
AsyncTuple.handleAll(prefix, state, action, process)
}
}
}
如果,mapStateToProps 进行数据加工,则会产生一些衍生数据。衍生数据类型可以用如下方法产生:
function mapStateToProps(state: GlobalState, props: Props) {
return ...;
}
type ReactProps = ReturnType<typeof mapStateToProps> & Props & typeof actions;
const rootReducers = {
a: AReducer,
b: BReducer,
};
const rootReducer = combineReducer(rootReducers);
export type RootState = ReturnState<typeof rootReducers>;
vscode IDE 可以安装 nelfe-toolkits。
- 1、支持 redux 文件的 snippets
- 2、可以按 cmd + ctrl + a,然后根据提示创建 action。
- 3、持续添加中...
除了用 handleAll 来避免异步 action 的冗余代码,简单的 action 也可以用如下方式避免冗余代码:
const actions = {
// 同时传入 key 和 value。
setConfig<Key extends keyof Config>(key: Key, value: Config[Key]) {
return {
type: Types.setConfig,
payload: {
key,
value
}
};
},
};
reducer:
case Types.setConfig: {
const { payload } = action;
return {
...state,
config: {
...state.config,
[payload.key]: payload.value
}
};
}