背景
为了使 dva 搭配 react hooks 带来更好的 typescript 支持,提高代码的可维护性,对dva的写法进行了改造,使用 class 的写法带来更好的类型支持。 同时结合 umi 3.x 带来更好的开发体验。
思路
- 设计 dvaModel(类装饰器),reducer(方法装饰器),effect(方法装饰器),state(属性装饰器),subscription(方法装饰器)等来改变 class 的行为。
- 通过装饰器,在 class 原型链 上,根据不同的装饰器类型,将方法转化成 dva model 相对应的结构。
- 根据不同的装饰器,改变方法,保持方法的传入参数不变,改变方法的返回值为 redux action。
- 兼容原生的 dispatch 方法。
- 依赖 umi 3 的插件机制,自动收集 state 的类型定义 和 自动实例化 class, 加载到全局容器中。
使用
npm install dva-model-enhance
umi 3.x
结合 umi 3.x 脚手架,自动加载 class 形式的 model,同时能够自动实例化 class 同时加载到 全局容器 modelsContainer 中。并且按照一定的约定,自动收集 class model 导出的 State 类型。参考例子 umi-example
需要搭配 umi-plugin-dva-enhance 使用
在 umi 项目中
npm install dva-model-enhance
npm install umi-plugin-dva-enhance
修改 umi 配置
// /.umirc.ts
export default {
dva: {
hmr: true,
// 默认为 false,且必须 设置 false,否则 plugin-dva 会重复加载 model
skipModelValidate: false
},
"dva-enhance": {
// class model 中对外导出的 state 名称, 生成的 StoreState.ts 中需要引用
// renderStateName?: (namespace: string, path: string) => string;
// 是否跳过 class model 验证, 跳过后生成的文件 namespace 默认取 文件名称, 默认值 false;
// skipClassModelValidate?: boolean;
},
// 如果提示 plugin umi-plugin-dva-enhance is already registered, 就不用显示添加插件
plugins: ["umi-plugin-dva-enhance"]
};
在 src/models 下任意添加一个 原生的 model, 来强制启用 @umijs/plugin-dva 插件相关功能,举例如下
/**
* 添加了一个 dva model, 用于开启 @umijs/plugin-dva 插件
* 插件内部判断 没有 符合条件的 model 时 不启用 dva 相关功能
*/
export default {
namespace: "__enableDva",
state: {}
};
修改写法
export default {
namespace: 'test',
state: { count: 0 },
effects: {
*initCount({ payload }, { put, call }) {
const result = yield call(() => Promise.resolve(100));
yield put(
this.setState({
count: result
})
);
}
},
reducers: {
setState(state, { payload }) {
return {
...state,
...payload,
}
},
addCount(state) {
return {
...state,
count: state.count + 1,
};
}
}
}
修改为 class 写法
import { effect, reducer, dvaModel, BaseModel } from "dva-model-enhance";
import StoreState from 'umi';
interface TestLocalState {
count: number;
}
export interface TestState extends TestLocalState {}
@dvaModel<TestLocalState>({
namespace: "test",
state: {
count: 0
}
})
class Test extends BaseModel<TestState, StoreState> {
@effect()
*initCount() {
const result = yield this.effects.call(() => Promise.resolve(100));
yield this.effects.put(
this.setState({
count: result
})
);
}
@reducer
addCount() {
return {
...this.state,
count: this.state.count + 1
};
}
}
export default Test;
如下 可以在 umi 中直接导出 actions 和 StoreState
- actions 是所有 class model 实例
- StoreState 是所有 class model 文件下 导出的类型集合
- 使用 dva-model-enhance 中的 useDispatch 传入 actions 可以提供 dva 方法的类型提示
import React, { useEffect } from "react";
import { useSelector, actions, StoreState } from "umi";
import { useDispatch } from "dva-model-enhance";
export default () => {
const dispatch = useDispatch(actions);
const state = useSelector((state: StoreState) => state);
useEffect(() => {
console.log(state.test, "state");
}, []);
return (
<div>
{state.test.count}
<button
onClick={() => {
dispatch.test.addCount();
}}
>
click
</button>
</div>
);
};
和 dva 一起使用
- 编写 model
import { effect, reducer, dvaModel, BaseModel } from "dva-model-enhance";
export interface TestState {
count: number;
}
// 全部的 redux 节点状态,需要在外面统一维护
interface StoreState {
test: TestState
}
@dvaModel<TestState>({
namespace: "test",
state: {
count: 0
}
})
class Test extends BaseModel<TestState, StoreState> {
@effect({ type: 'takeLatest' })
*initCount() {
const result: number = yield this.effects.call(() => Promise.resolve(100));
yield this.effects.put(this.setCount(result));
}
@reducer
setCount(count: number) {
return {
...this.state,
count,
};
}
}
- dva 加载 mdel
import dva from 'dva';
import { getModel } from 'dva-model-enhance';
const app = dva({
namespacePrefixWarning: false, // 取消 dva 的警告
});
app.model(getModel(Test)) // dva 加载 class 形式的 mdel
- 在组件中使用
import React from 'react';
import { useDispatch } from 'dva';
import { modelsContainer } from 'dva-model-enhance';
// 引入编写的 class 形式的 model
import Test from './models/test';
const test = new Test();
/**
* 向 models 容器中添加 以 namespace 为 key 的实例
* 确保所有model对象是单例,这是为了让
* dispatch({
* type: 'test/setCount',
* payload: [50],
* })
* 等价于
* dispatch(test.setCount(50))
*
* 这种形式的写法 能正常访问 class model 中的方法
*/
modelsContainer.set('test', test);
interface Props {}
const App: React.FC<Props> = ({ }) => {
const dispatch = useDispatch();
const handleClick = () => {
dispatch(
test.setCount(50)
)
}
return (
<div>
count: {count}
<button onClick={handleClick}>add</button>
</div>
);
};
export default App;
- 优化 dispatch 的使用
将 class model 统一管理,希望通过 dispatch 能直接访问到 model 的方法,并具有类型提示
// ./actions.ts
import { modelsContainer } from 'dva-model-enhance';
// 引入编写的 class 形式的 model
import Test from './models/test';
const actions = {
test: new Test(),
}
modelsContainer.put(actions);
export default actions;
import React from 'react';
import { useDispatch } from 'dva-model-enhance';
import actions from './actions'
interface Props {}
const App: React.FC<Props> = ({ }) => {
const dispatch = useDispatch<typeof actions>(actions);
const handleClick = () => {
dispatch.test.setCount(50)
}
return (
<div>
count: {count}
<button onClick={handleClick}>add</button>
</div>
);
};