深度解析

UmiJS:企业级前端应用框架的最佳实践

深入探索 UmiJS 的核心架构,理解约定式路由、插件体系和微前端如何赋能企业级应用开发

2024年12月8日18 分钟822 阅读
UmiJSReact企业级框架
UmiJS:企业级前端应用框架的最佳实践

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-antdAnt 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 带来的极速构建体验,更是让开发效率提升到新的高度。

文章标签

# UmiJS# React# 企业级框架
返回首页