前言
TypeScript + React 类型安全三件套:Component、Redux、和Service类型化。
在 redux 类型化(1) 里提到了 model——一种组织 state、reducers 和 effects 代码的数据结构,本节将介绍如何实现一个类型化 model 工厂。
Redux类型化——如何实现类型安全的model
Basic Interface
定义类型 Action
:
interface Action<P> {
payload: P;
}
参照 dvajs model 的接口设计,一个 model 最核心的字段至少应该包括 state, reducers 和 effects:
interface Model {
state: any;
reducers: {
[doSomething: string]: (state: any, action: Action<any>) => any;
};
effects: {
[doSomethingAsync: string]: (action: Action<any>, utils: SagaUtils) => Iterator<any, any>;
};
}
其中 Model.state
和 reuders.doSomething.state
应该是贯通的,后者的类型,应该由前者推断可得,这时就需要泛型:
interface Model<S extends any> {
state: S;
reducers: {
[doSomething: string]: (state: S, action: Action<any>) => S;
};
effects: {
[doSomethingAsync: string]: (action: Action<any>, utils: SagasUtils) => Iterator<{}, any, any>;
};
}
则函数 app.model
类型则可以是这样:
interface CreateModel {
<S>(model: Model<S>): {};
}
App Model
这样我们就得到一个简陋的类型安全 model 工厂:
const UserModel = app.model<{ id?: number; name?: string }>({
state: {},
reducers: {
// TODO: 泛型的痛点
doUser: (state, action: Action<number>) => ({ ...state, id: action.payload })
},
effects: {
*doUserAsync(action: Action<number>, utils): Iteractor<{}, any, any> {
// TODO: 非类型化
const user = yield utils.call(xxx, action.payload);
// TODO: 非类型化
yield utils.put('user/doUser');
}
}
})
因为我们在 model 的类型定义里,显式的标注了每个 effect 第二个参数的类型,以及通过泛型贯通了每个 reducer 第一个参数和 state 的类型,因而实际在编写 model 代码的时候,仅需需要显示的标注 state 和 每个 action 的类型就可以了。
TODO
以上的尝试,大致满足了编写 model 时候 state 与 reducers 类型的贯通;但在实际应用中,我们需要也能做到更全面的类型贯通,比如:
- effect 逻辑里 call、put 的类型化
- 由 reducers、effects 映射生成的 action 的类型化
- 不仅仅局限于 redux model,还有 hooks model
- 不仅仅是 generator effects,还有 async & await effects
下一节里,将详尽的介绍 tkit-model [@tefe/model] 是如何实现类型安全的。