020 Umi@4 中如何实现动态菜单
在数据流章节中我有一段这样的描述:
为什么都不是最佳实践了,我还要一直提 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 即可。
总结
以上操作,看起来比较繁琐,但是如果你对各个概念都有了一定了解,那阅读起来就会很轻松,觉得逻辑非常的清晰。如果你有任何疑问,可以去看看前面的课程,也可以在评论区和我互动。
你应该可以从我的行文内容看出来,我是没有任何“存稿”的,跟这个系列文章,有点类似半直播的方式。我觉得这比我自己“埋头苦干”,要有趣的多,也希望你会喜欢。
转载自:https://juejin.cn/post/7112781095208222756