UmiJS:企业级前端应用框架的最佳实践
UmiJS 是由蚂蚁金服团队打造的可插拔企业级前端应用框架,它以约定优于配置的理念,为 React 应用开发提供了开箱即用的最佳实践方案。本文将深入剖析 UmiJS 的核心架构和实战应用。
UmiJS 核心理念
UmiJS 的设计哲学围绕三个核心原则:约定优于配置、插件化架构 和 开箱即用。它不仅是一个框架,更是一套完整的企业级解决方案。
┌─────────────────────────────────────────────────────────────┐
│ UmiJS 架构全景 │
├─────────────────────────────────────────────────────────────┤
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │ 应用层 │ │ 插件层 │ │ 运行时层 │ │
│ │ ───────── │ │ ───────── │ │ ───────────────── │ │
│ │ Pages │ │ @umijs/ │ │ 路由 / 数据流 │ │
│ │ Layouts │ │ plugins │ │ 国际化 / 权限 │ │
│ │ Components │ │ 自定义插件 │ │ 请求 / 微前端 │ │
│ └─────────────┘ └─────────────┘ └─────────────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ 构建层 (MFSU) │
│ Webpack 5 / Vite | esbuild | Module Federation │
└─────────────────────────────────────────────────────────────┘
约定式路由系统
UmiJS 最强大的特性之一是约定式路由,通过文件系统自动生成路由配置。
目录结构与路由映射
src/pages/
├── index.tsx → /
├── users/
│ ├── index.tsx → /users
│ ├── [id].tsx → /users/:id
│ ├── [id]/profile.tsx → /users/:id/profile
│ └── [...all].tsx → /users/* (通配符)
├── posts/
│ ├── index.tsx → /posts
│ └── [postId]/
│ ├── index.tsx → /posts/:postId
│ └── comments.tsx → /posts/:postId/comments
└── 404.tsx → 404 页面
动态路由与参数获取
typescript// src/pages/users/[id].tsx import { useParams, useSearchParams } from 'umi' interface UserPageProps { // 类型安全的路由参数 } export default function UserPage() { // 获取动态路由参数 const { id } = useParams<{ id: string }>() // 获取查询参数 const [searchParams, setSearchParams] = useSearchParams() const tab = searchParams.get('tab') || 'profile' return ( <div className="user-page"> <h1>用户 ID: {id}</h1> <Tabs activeKey={tab} onChange={(key) => setSearchParams({ tab: key })} > <TabPane tab="资料" key="profile"> <UserProfile userId={id} /> </TabPane> <TabPane tab="订单" key="orders"> <UserOrders userId={id} /> </TabPane> </Tabs> </div> ) }
路由配置增强
typescript// .umirc.ts import { defineConfig } from 'umi' export default defineConfig({ routes: [ { path: '/', component: '@/layouts/BasicLayout', routes: [ { path: '/', component: '@/pages/Home' }, { path: '/admin', component: '@/layouts/AdminLayout', // 路由级别的权限控制 wrappers: ['@/wrappers/auth'], routes: [ { path: '/admin/users', component: '@/pages/admin/Users' }, { path: '/admin/settings', component: '@/pages/admin/Settings' }, ], }, { path: '/dashboard', component: '@/pages/Dashboard', // 路由元信息 title: '控制台', icon: 'dashboard', }, ], }, ], })
插件体系架构
UmiJS 的插件系统是其核心竞争力,提供了极致的可扩展性。
插件生命周期
typescript// plugin-example.ts import { IApi } from 'umi' export default (api: IApi) => { // 描述插件信息 api.describe({ key: 'myPlugin', config: { schema(joi) { return joi.object({ enabled: joi.boolean(), options: joi.object(), }) }, }, }) // 修改配置阶段 api.modifyConfig((memo) => { memo.title = memo.title || '默认标题' return memo }) // 修改路由阶段 api.modifyRoutes((routes) => { return [ ...routes, { path: '/plugin-page', component: require.resolve('./PluginPage'), }, ] }) // 生成临时文件 api.onGenerateFiles(() => { api.writeTmpFile({ path: 'plugin-exports.ts', content: ` export const pluginConfig = ${JSON.stringify(api.config.myPlugin)}; export const version = '1.0.0'; `, }) }) // 添加运行时代码 api.addRuntimePlugin(() => [require.resolve('./runtime')]) // 构建完成钩子 api.onBuildComplete(({ err, stats }) => { if (!err) { console.log('构建完成!') } }) }
常用官方插件
| 插件 | 功能 | 使用场景 |
|---|---|---|
@umijs/plugin-access | 权限管理 | 页面/组件级权限控制 |
@umijs/plugin-antd | Ant Design 集成 | 企业级 UI 组件 |
@umijs/plugin-dva | 数据流管理 | 复杂状态管理 |
@umijs/plugin-initial-state | 全局初始状态 | 用户信息/配置初始化 |
@umijs/plugin-locale | 国际化 | 多语言支持 |
@umijs/plugin-model | 简易数据流 | 轻量级状态共享 |
@umijs/plugin-qiankun | 微前端 | 子应用集成 |
@umijs/plugin-request | 请求层 | API 调用封装 |
数据流方案
UmiJS 提供了多层次的数据流解决方案,从轻量到重量级应对不同场景。
useModel:轻量级状态共享
typescript// src/models/user.ts import { useState, useCallback } from 'react' import { request } from 'umi' export default function useUserModel() { const [user, setUser] = useState<User | null>(null) const [loading, setLoading] = useState(false) const fetchUser = useCallback(async () => { setLoading(true) try { const data = await request('/api/user/current') setUser(data) } finally { setLoading(false) } }, []) const logout = useCallback(async () => { await request('/api/auth/logout', { method: 'POST' }) setUser(null) }, []) const hasPermission = useCallback( (permission: string) => { return user?.permissions?.includes(permission) ?? false }, [user] ) return { user, loading, fetchUser, logout, hasPermission, } } // 组件中使用 import { useModel } from 'umi' function UserProfile() { const { user, loading, logout } = useModel('user') if (loading) return <Spin /> return ( <div> <Avatar src={user?.avatar} /> <span>{user?.name}</span> <Button onClick={logout}>退出</Button> </div> ) }
DVA:Redux 最佳实践
typescript// src/models/products.ts import { Effect, Reducer, Subscription } from 'umi' interface ProductsModelState { list: Product[] pagination: Pagination loading: boolean } interface ProductsModelType { namespace: 'products' state: ProductsModelState effects: { fetch: Effect create: Effect update: Effect delete: Effect } reducers: { save: Reducer<ProductsModelState> setLoading: Reducer<ProductsModelState> } subscriptions: { setup: Subscription } } const ProductsModel: ProductsModelType = { namespace: 'products', state: { list: [], pagination: { current: 1, pageSize: 10, total: 0 }, loading: false, }, effects: { *fetch({ payload }, { call, put }) { yield put({ type: 'setLoading', payload: true }) try { const response = yield call(fetchProducts, payload) yield put({ type: 'save', payload: { list: response.data, pagination: response.pagination, }, }) } finally { yield put({ type: 'setLoading', payload: false }) } }, *create({ payload, callback }, { call, put }) { const response = yield call(createProduct, payload) if (response.success) { yield put({ type: 'fetch' }) callback?.() } }, *update({ payload }, { call, put, select }) { const { id, ...data } = payload yield call(updateProduct, id, data) // 乐观更新 const list = yield select((state: any) => state.products.list) yield put({ type: 'save', payload: { list: list.map((item: Product) => item.id === id ? { ...item, ...data } : item ), }, }) }, *delete({ payload: id }, { call, put }) { yield call(deleteProduct, id) yield put({ type: 'fetch' }) }, }, reducers: { save(state, { payload }) { return { ...state, ...payload } }, setLoading(state, { payload }) { return { ...state, loading: payload } }, }, subscriptions: { setup({ dispatch, history }) { return history.listen(({ pathname }) => { if (pathname === '/products') { dispatch({ type: 'fetch' }) } }) }, }, } export default ProductsModel
权限管理方案
UmiJS 提供了完整的权限管理解决方案。
权限配置
typescript// src/access.ts export default function access(initialState: { currentUser?: User }) { const { currentUser } = initialState || {} return { // 角色权限 isAdmin: currentUser?.role === 'admin', isSuperAdmin: currentUser?.role === 'super_admin', // 功能权限 canViewUsers: currentUser?.permissions?.includes('user:view'), canEditUsers: currentUser?.permissions?.includes('user:edit'), canDeleteUsers: currentUser?.permissions?.includes('user:delete'), // 动态权限函数 canAccessRoute: (route: { authority?: string[] }) => { if (!route.authority) return true return route.authority.some(auth => currentUser?.permissions?.includes(auth) ) }, // 数据权限 canEditRecord: (record: { creatorId: string }) => { return ( currentUser?.role === 'admin' || record.creatorId === currentUser?.id ) }, } }
权限组件使用
tsx// 路由级权限控制 // src/wrappers/auth.tsx import { Navigate, useAccess, useModel } from 'umi' export default ({ children }: { children: React.ReactNode }) => { const { isLogin } = useModel('@@initialState') if (!isLogin) { return <Navigate to="/login" replace /> } return <>{children}</> } // 组件级权限控制 import { useAccess, Access } from 'umi' function UserManagement() { const access = useAccess() return ( <div> <h1>用户管理</h1> {/* 声明式权限 */} <Access accessible={access.canEditUsers}> <Button type="primary">新增用户</Button> </Access> {/* 带 fallback */} <Access accessible={access.canDeleteUsers} fallback={<Button disabled>删除</Button>} > <Button danger>删除</Button> </Access> {/* 编程式权限 */} <Table columns={[ ...baseColumns, ...(access.isAdmin ? adminColumns : []), ]} dataSource={users} /> </div> ) }
微前端集成(Qiankun)
UmiJS 深度集成了 Qiankun 微前端方案。
主应用配置
typescript// .umirc.ts(主应用) export default { qiankun: { master: { apps: [ { name: 'sub-app-react', entry: '//localhost:8001', credentials: true, }, { name: 'sub-app-vue', entry: '//localhost:8002', }, ], // 沙箱配置 sandbox: { strictStyleIsolation: true, experimentalStyleIsolation: true, }, // 预加载 prefetch: 'all', }, }, }
tsx// src/pages/micro-apps.tsx import { MicroApp, MicroAppWithMemoHistory } from 'umi' export default function MicroAppsPage() { return ( <div className="micro-container"> {/* 基础使用 */} <MicroApp name="sub-app-react" // 传递给子应用的 props globalData={{ token: 'xxx', userInfo: {} }} // 子应用加载状态 loader={(loading) => loading ? <Spin /> : null} /> {/* 带路由同步 */} <MicroAppWithMemoHistory name="sub-app-vue" url="/vue-app" /> </div> ) }
子应用配置
typescript// .umirc.ts(子应用) export default { qiankun: { slave: {}, }, // 开发模式下独立运行 base: process.env.QIANKUN ? '/sub-app/' : '/', publicPath: process.env.QIANKUN ? '/sub-app/' : '/', }
typescript// src/app.ts(子应用生命周期) export const qiankun = { // 应用加载前 async bootstrap(props: any) { console.log('子应用 bootstrap', props) }, // 应用挂载 async mount(props: any) { console.log('子应用 mount', props) // 接收主应用传递的数据 const { globalData, setGlobalState, onGlobalStateChange } = props // 监听全局状态变化 onGlobalStateChange((state: any, prev: any) => { console.log('全局状态变化', state, prev) }) }, // 应用卸载 async unmount(props: any) { console.log('子应用 unmount') }, }
MFSU 极速构建
MFSU(Module Federation Speed Up)是 UmiJS 独创的构建加速方案。
typescript// .umirc.ts export default { // 开启 MFSU mfsu: { // 策略:normal | eager strategy: 'normal', // 共享依赖 shared: { react: { singleton: true }, 'react-dom': { singleton: true }, }, // 排除的依赖 exclude: ['lodash'], }, }
性能对比
传统构建(Webpack 5):
├── 首次构建: 45s
├── 热更新: 2-5s
└── 依赖变更: 45s
MFSU 构建:
├── 首次构建: 20s(依赖预构建)
├── 热更新: 200-500ms
└── 依赖变更: 5s(增量构建)
Vite 模式:
├── 首次构建: 5s
├── 热更新: 50-100ms
└── 依赖变更: 3s
请求层封装
typescript// src/app.ts import { RequestConfig } from 'umi' export const request: RequestConfig = { timeout: 10000, // 请求前缀 prefix: process.env.API_BASE_URL, // 错误处理 errorConfig: { errorHandler: (error: any) => { const { response } = error if (response?.status === 401) { // 跳转登录 history.push('/login') } else if (response?.status === 403) { message.error('无权限访问') } else if (response?.status >= 500) { message.error('服务器错误') } throw error }, errorThrower: (res: any) => { if (!res.success) { const error: any = new Error(res.message) error.name = 'BizError' error.info = res throw error } }, }, // 请求拦截器 requestInterceptors: [ (config: any) => { const token = localStorage.getItem('token') if (token) { config.headers.Authorization = `Bearer ${token}` } return config }, ], // 响应拦截器 responseInterceptors: [ (response: any) => { // 统一处理响应 const { data } = response if (data?.code !== 0) { message.error(data?.message || '请求失败') } return response }, ], }
最佳实践与项目结构
├── config/ # 配置文件
│ ├── config.ts # 主配置
│ ├── config.dev.ts # 开发环境
│ ├── config.prod.ts # 生产环境
│ ├── routes.ts # 路由配置
│ └── proxy.ts # 代理配置
├── mock/ # Mock 数据
│ ├── user.ts
│ └── products.ts
├── src/
│ ├── .umi/ # 临时文件(自动生成)
│ ├── access.ts # 权限配置
│ ├── app.ts # 运行时配置
│ ├── global.less # 全局样式
│ ├── components/ # 公共组件
│ │ ├── PageContainer/
│ │ ├── ProTable/
│ │ └── SearchForm/
│ ├── layouts/ # 布局组件
│ │ ├── BasicLayout/
│ │ └── BlankLayout/
│ ├── models/ # 数据模型
│ │ ├── global.ts
│ │ └── user.ts
│ ├── pages/ # 页面组件
│ │ ├── Home/
│ │ ├── Dashboard/
│ │ └── User/
│ ├── services/ # API 服务
│ │ ├── user.ts
│ │ └── product.ts
│ ├── utils/ # 工具函数
│ │ ├── format.ts
│ │ └── validate.ts
│ └── wrappers/ # 路由包装器
│ └── auth.tsx
├── public/ # 静态资源
├── package.json
└── tsconfig.json
总结
"UmiJS 不仅是一个前端框架,更是蚂蚁金服多年企业级应用开发经验的结晶。它通过约定式路由降低配置成本,通过插件体系提供无限扩展可能,通过微前端方案解决大型应用的协作难题。
选择 UmiJS 意味着选择了一套经过大规模验证的企业级解决方案。无论是中后台管理系统、微前端架构还是复杂的业务应用,UmiJS 都能提供开箱即用的最佳实践。MFSU 带来的极速构建体验,更是让开发效率提升到新的高度。



