Dva Types
Dva 扩展类型
源自迁移 Dva 项目到 typescript 有感

用法
1. 安装依赖
npm i -d @moka-fe/dva-types
2. 引入
import { ModelAction, ModelState, Commands, Result } from '@moka-fe/dva-types';
一共有 4 个工具类型,分别是:
ModelAction
,根据 model 自动推导出 action 类型
ModelState
,根据 model 自动推导出 state 类型
Commands
,redux saga 相关的 commands 类型
Result
,generator yield 的类型
具体用法见下面的例子。
3. 使用
假设我们在开发一个经典的待办应用(Todo List),原本的 model 可能是这样:
const todoModel = {
namespace: 'todo',
state: {
todos: [],
},
reducers: {
set(_, { payload }) {
return { todos: payload };
},
add({ todos }, { payload }) {
return {
todos: [...todos, payload],
};
},
},
effects: {
*fetch(_, { call, put }) {
const todos = yield call(fetchTodos);
if (todos) {
yield put({
type: 'set',
payload: todos,
});
}
},
},
};
export default todoModel;
先看一下改造后的样子:
type Todo = {
id?: number;
summary: string;
createDate: Date;
status: 'todo' | 'doing' | 'done';
};
type TodoModel = {
namespace: 'todo';
state: {
todos: Todo[];
};
reducers: {
set(state: TodoState, action: { payload: Todo[] }): TodoState;
add(state: TodoState, action: { payload: Todo }): TodoState;
};
effects: {
fetch(_: never, { call, put }: Commands<TodoModel>): void;
};
};
const todoModel: TodoModel = {
namespace: 'todo',
state: {
todos: [],
},
reducers: {
set(_, { payload }) {
return { todos: payload };
},
add({ todos }, { payload }) {
return {
todos: [...todos, payload],
};
},
},
effects: {
*fetch(_, { call, put }) {
const todos = yield call(fetchTodos);
if (todos) {
yield put({
type: 'set',
payload: todos,
});
}
},
},
};
export default todoModel;
export type TodoState = ModelState<TodoModel>;
export type TodoAction = ModelAction<TodoModel>;
其实简单来讲,就是 2 点:
- 手动补充了 model 类型
- 额外导出了一些类型(state 类型和 action 类型)
关于补充 model 类型,这里有一些注意点:
reducers 相对较简单,类型的写法是这样的:
reducers: {
add(state: TodoState, action: { payload: Todo }): TodoState;
...
}
effects 相对多一些东西:
import { Commands } from '@moka-fe/dva-types';
effects: {
fetch(_: never, commands: Commands<TodoModel>): void;
remove(action: { payload: { id: number } }, commands: Commands<DashboardModel>): void;
...
}
定义好 reducers 和 effects 的类型后,ts 就可以自动推导出这个 model 里所有 action 的类型是什么,这样就可以在 reducer 和 effect 方法里做校验了,例如:
*fetch(_, { call, put }) {
const todos = yield call(fetchTodos);
if (todos) {
yield put({
type: 'set',
payload: todos,
});
}
},
有时候 effect 可能是个数组,例如用到了 saga 的 takeLatest:
const takeLatest = { type: 'takeLatest' };
const model = {
effects: {
fetchSomething: [
function* fetchSomething(_, { call, put }) {
...
},
takeLatest,
],
},
};
对于这种数组类型的,只需要在 model 的类型中写好 tuple 类型即可,例如:
const takeLatest = { type: 'takeLatest' };
interface SomethingModel = {
effects: {
fetchSomething: [(_: never, Commands<SomethingModel>) => void, typeof takeLatest];
};
};
如果用到了 select,写法跟普通的 yield 类似,在 yield 前面补充类型即可,例如:
const { todos }: TodoState = yield select(({ todos }) => todos);
其实是手动写了个 TodoState 的类型,这是因为 generator 的原理决定了这里的 yield 类型无法自动推导出来,必须手动补充,所以算是有一些些代码冗余吧,美中不足。
关于导出的类型,也需要说明一下
额外导出的类型是通过一个工具类型 ModelAction 做的:
export type TodoAction = ModelAction<TodoModel>;
这个导出的 action 类型可以用在 dispatch 的地方,例如:
dispatch<TodoAction>({ type: 'todo/add', payload: blablabla });
另外一个额外导出的是 state 的类型:
export type TodoState = ModelState<TodoModel>;
这个导出的 state 类型可以用在 selector 处,例如:
const { todos } = useSelector(
({ todoState }: { todoState: DashboardState }) => todoState.todos,
shallowEqual
);
TODO