Umi项目实战中遇到的路由配置与插件调试坑点总结

馨然 Dev 框架 阅读 2,031
赞 10 收藏
二维码
手机扫码查看
反馈

项目初期的技术选型

去年下半年接了个内部中后台系统,需求挺典型:权限粒度细、菜单动态加载、多级嵌套路由、需要 SSR 支持(老板说“要对 SEO 友好”,虽然我知道中后台根本没人搜…但他说了就得做)、还要支持微前端接入预留。团队之前用过 Umi 2,但没深挖;React 生态里比来比去,还是觉得 Umi 3/4 这套约定式 + 插件化 + 内置 Ant Design 集成的组合,省事程度吊打自己从零搭 webpack + react-router + dva 的老路。

Umi项目实战中遇到的路由配置与插件调试坑点总结

关键是——它真能跑 umi build --ssr 出来一个可部署的 Node 服务,这点我们试了 Next.js,配置一多就容易崩在 getServerSideProps 的 context 类型上,折腾两天放弃。Umi 虽然文档有时候像谜语,但至少 build 出来的东西能跑。

最大的坑:性能问题

上线前压测,首页首屏 TTFB 1.8s,SSR 渲染后 hydration 卡顿明显,Chrome Performance 面板一看:大量时间花在 useEffect 里重复请求用户权限、菜单、字典项。每个页面组件都写了类似的逻辑:

// pages/dashboard/index.tsx
import { useEffect, useState } from 'react';
import { useModel } from 'umi';

export default function Dashboard() {
  const [menu, setMenu] = useState([]);
  const [perms, setPerms] = useState([]);

  useEffect(() => {
    // ❌ 每次组件挂载都发请求,SSR 下还重复执行
    fetch('/api/menu').then(r => r.json()).then(setMenu);
    fetch('/api/perms').then(r => r.json()).then(setPerms);
  }, []);

  return <div>仪表盘</div>;
}

开始没想到,以为是 SSR 缓存没开。后来查了 Umi 的 SSR 文档才发现:它默认不帮你做数据预取(data preloading),useEffect 在服务端是不执行的,但客户端 hydration 后立刻触发——等于把本该在服务端干完的事,拖到客户端再干一遍,还干两次(SSR 渲染一次空壳,hydrate 后重刷)。

改!必须把数据捞到服务端去。Umi 提供了 getInitialProps(类 Next 的写法),但只支持页面组件,且不能用 hooks。我们项目里全是函数组件 + hooks,硬切 class 太反人类。后来发现 Umi 4 的 getServerSideProps 是支持的(得配 ssr: { mode: 'stream' }),但文档藏得深,示例代码还少得可怜。

最终的解决方案

最后用了这个折中但亲测有效的方案:在 app.ts 里统一拦截路由变更,提前拉数据,再透传给页面。核心是利用 Umi 的 modifyClientRenderOpts 和自定义 render 流程,但太重。我们退了一步——用 layout 组件兜底。

新建 src/layouts/BasicLayout.tsx,在里面用 useModel('global') 做初始化请求,并加个 loading 状态防止闪烁。关键点是:所有页面都必须用这个 layout,且全局 model 的初始化逻辑只跑一次:

// src/models/global.ts
import { history, request } from 'umi';

export default {
  state: {
    menu: [],
    perms: [],
    loading: true,
  },
  effects: {
    *fetchInitData(_, { put, call }) {
      yield put({ type: 'updateState', payload: { loading: true } });
      try {
        const [menu, perms] = yield Promise.all([
          call(() => request('/api/menu')),
          call(() => request('/api/perms')),
        ]);
        yield put({
          type: 'updateState',
          payload: { menu, perms, loading: false },
        });
      } catch (e) {
        yield put({ type: 'updateState', payload: { loading: false } });
      }
    },
  },
  reducers: {
    updateState(state, { payload }) {
      return { ...state, ...payload };
    },
  },
};
// src/layouts/BasicLayout.tsx
import { useModel } from 'umi';
import { useEffect } from 'react';

export default function BasicLayout({ children }) {
  const { initialState, fetchInitData } = useModel('global');

  useEffect(() => {
    if (!initialState.menu?.length) {
      fetchInitData();
    }
  }, [initialState.menu?.length, fetchInitData]);

  if (initialState.loading) {
    return <div>加载中...</div>;
  }

  return <>{children}</>;
}

然后在 src/app.ts 里指定默认 layout:

// src/app.ts
export const layout = {
  layout: '@/layouts/BasicLayout',
};

这样所有页面共享初始化状态,避免重复请求。实测 SSR 首屏 TTFB 降到 600ms 左右,hydration 也顺滑多了。当然,这里有个小尾巴没完全解决:如果用户快速跳转到新页面,而 global model 还在 loading,会出现短暂空白。我们加了个本地缓存 fallback(localStorage 存 5 分钟的 menu),不影响主流程,就不深究了。

插件踩坑提醒:@umijs/plugin-qiankun

微前端部分我们用 qiankun 接入另一个 Vue 子应用。Umi 官方插件 @umijs/plugin-qiankun 看起来很美好,但实际配起来有俩大坑:

  • 子应用生命周期钩子不触发:文档说自动注册,结果 mount/unmount 死活不进。查了半天发现是 Umi 4 默认启用了 fastRefresh,跟 qiankun 的 patch 逻辑冲突。关掉就行:fastRefresh: false 加到 config.ts 里。
  • 样式隔离失效:qiankun 的 strictStyleIsolation 在 Umi 里默认不生效。得手动在子应用入口加一句:__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;,不然 CSS 加载路径错乱。

这两点文档里都没强调,全靠翻 GitHub issues 和源码才搞明白。建议你用前先搜下最新 issue,别信文档。

回顾与反思

整体来看,Umi 对中后台项目确实友好:约定大于配置省了 70% 的基建时间,plugin 体系也能覆盖大部分扩展需求(比如我们加了自定义的权限指令插件,一行 v-access="user:delete" 就控制按钮显隐)。但它的「黑盒感」依然存在——特别是 SSR 和微前端这种深度集成场景,出问题很难 debug,源码跳转像闯迷宫。

做得好的地方:Ant Design 主题定制顺利(config.ts 里直接写 antd: { theme: { ... } }),国际化切换无痛(useIntl + formatMessage),还有 umi dev 的热更新基本不崩(比我们以前用 craco 强太多)。

还能优化的:Umi 的 TypeScript 类型提示偶尔抽风,尤其在自定义 plugin 里,经常要手动加 // @ts-ignore;另外文档搜索功能弱,关键词一多就找不到对应 API,建议多逛 GitHub Discussions。

以上是我踩坑后的总结,希望对你有帮助。如果你也在用 Umi 做 SSR 或 qiankun 集成,欢迎评论区交流,尤其是有没有更干净的数据预取方案?我们这套现在勉强能跑,但总觉得不够优雅。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论