在数据流章节中我有一段这样的描述:
为什么都不是最佳实践了,我还要一直提 dva
因为纯 hooks 的数据流方案,存在天然的局限性,因为 react hook 只能用在 react 的上下文环境中,但是在 Umi 中我们还有一些环节是不在 react 的上下文的,比如如果我们要前置判断用户登录情况,或者提前获取用户可访问菜单数据,或者其他的一些项目前置数据,我们都需要“上升”我们的数据流方案。
其实这一段我是想描述在 Umi 项目中还存在不在 React 生命周期中的数据流管理时机,所以我们依旧需要 dva 来管理我们的项目数据,其实最主要的点还是 dva 是一个很流行的数据流管理方案,在我们的项目中有很长的使用情况,团队内对它都比较熟悉,因此就算出现了其他可替代的方案,我们依旧会选择使用较为“古老”的方案。
西门吹风凉飕飕 提到的疑惑,其实在第 10 课使用 Umi 配置,定制化你自己的 Umi 框架中,我们讲解 Umi 中的运行时配置 - render
时,就已经演示过代码了。那时候,我们还没讲解到数据流和请求这些,今天我们就将这几个方案串联起来。
升级插件
将 @alita/plugins
升级到 3.0.3
,因为我们添加了一个获取 Dva app 的 Api ,这使你能够在任何的 js 环境中继续使用 Dva
"@alita/plugins": "3.0.3", 复制代码
`@alita/plugins` 和 `@umijs/plugins` 中的 dva 插件有什么差别吗?
其实这两个插件现在的功能是一致的,alita 中的 Dva 插件就是从 umijs 中复制出来的,唯一的不同是,alita 中的插件,添加了约定的 Dva module 类型定义。可以更加规范的在 Typescript 中使用 Dva。
Mock 数据
在第 16 课 Umi 项目中的菜单与权限 中,我们讲解了 Umi 项目中的菜单与权限,我们使用了unaccessible
数组来管理我们的菜单,所以我们想将它转移到本地的“服务端”。
新建 Mock 文件 mock/accessible.ts
export default { "POST /api/rule": { success: true, data: ["/hooks", "/useEffect", "/usemodel", "/useState"], }, }; 复制代码
如果你不知道这有什么用,请阅读第 18 课 Umi 中使用 mockjs 完善前后端分离
增加配置
import { defineConfig } from "umi"; export default defineConfig({ plugins: [ // 其他插件不用删除,这里只是简略展示 require.resolve("@alita/plugins/dist/dva"), ], // 其他配置不用删除,这里只是简略展示 dva: { enableModelsReExport: {}, }, }); 复制代码
enableModelsReExport
配置就是 alita 中的 Dva 插件特有的,会将 module 文件中的 State 类型导出,这有个要求,每个 module 必须写明 State 的类型,不然程序就会报错。通过约定,我们可以很方便的解决问题。
当然了如果你觉得这个功能你不需要,你可以不开启这个配置,或者直接使用 umijs/plugins 中的 Dva 插件。
api.config.dva?.enableModelsReExport ? models .map((model: { file: string; namespace: string }) => { const { file, namespace } = model; // prettier-ignore // export type { IndexModelState } from '/Users/xiaohuoni/next-alita-app/src/models/index'; return `export type { ${namespace.replace(/( |^)[a-z]/g, (L) => L.toUpperCase())}ModelState } from '${winPath(file.replace(extname(file), ''))}';`; }) .join('\r\n') : '' 复制代码
添加 Dva module 文件
新建 Dva module 文件 src/models/global.ts
import { Reducer } from "umi"; export interface GlobalModelState { unaccessible: string[]; } export interface GlobalModelType { namespace: "global"; state: GlobalModelState; reducers: { save: Reducer<GlobalModelState>; }; } const GlobalModel: GlobalModelType = { namespace: "global", state: { unaccessible: [], }, reducers: { save(state, action) { return { ...state, ...action.payload, }; }, }, }; export default GlobalModel; 复制代码
注意以上内容必须的是 GlobalModel 对象,剩余部分都是为了更好的用类型去定义和规范 GlobalModel 对象。
在 render 中发起请求
在运行时配置中 src/app.ts
的 render
中发起请求,如果你不知道 render
是啥,请翻阅第 10 课使用 Umi 配置,定制化你自己的 Umi 框架。
import { request, getDvaApp } from "umi"; export function render(oldRender: any) { request("/api/accessible").then(({ data }) => { const app = getDvaApp(); app?._store.dispatch({ type: "global/save", payload: { unaccessible: data }, }); }); oldRender(); } 复制代码
这里我们通过 getDvaApp
获取到当前项目中的 Dva app,然后使用 _store 上的 dispatch 发起一个 action 将数据更新到 global modules 中。
将 module 数据绑定到页面上
将 global 的数据,绑定到全局布局上, src/layouts/index.tsx
:
import { connect } from "umi"; import type { ConnectProps, GlobalModelState } from "umi"; interface AppProps extends ConnectProps { global: GlobalModelState; } const App: React.FC<AppProps> = ({ global }) => { const { unaccessible } = global; return (<></>) } export default connect(({ global }: { global: GlobalModelState }) => ({ global, }))(App); 复制代码
以上操作就将页面和 module 进行了双向绑定,只要 global 数据发生变化,就会促使页面进行重绘。
我们只需要取出 global 中的 unaccessible 代替原来“写死”的 unaccessible 即可。
总结
以上操作,看起来比较繁琐,但是如果你对各个概念都有了一定了解,那阅读起来就会很轻松,觉得逻辑非常的清晰。如果你有任何疑问,可以去看看前面的课程,也可以在评论区和我互动。
你应该可以从我的行文内容看出来,我是没有任何“存稿”的,跟这个系列文章,有点类似半直播的方式。我觉得这比我自己“埋头苦干”,要有趣的多,也希望你会喜欢。