020 Umi@4 中如何实现动态菜单

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第 22 天,点击查看活动详情

200.jpg

在数据流章节中我有一段这样的描述:

为什么都不是最佳实践了,我还要一直提 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.tsrender 中发起请求,如果你不知道 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 即可。

总结

以上操作,看起来比较繁琐,但是如果你对各个概念都有了一定了解,那阅读起来就会很轻松,觉得逻辑非常的清晰。如果你有任何疑问,可以去看看前面的课程,也可以在评论区和我互动。

你应该可以从我的行文内容看出来,我是没有任何“存稿”的,跟这个系列文章,有点类似半直播的方式。我觉得这比我自己“埋头苦干”,要有趣的多,也希望你会喜欢。

猜你喜欢

转载自juejin.im/post/7112781095208222756