UmiJS快速入门
本文面向具有 React 基础的开发者,快速介绍 UmiJS 4.x 的核心概念和开发方式。如果你已经熟悉 React 和基本的前端开发,这篇文章将帮助你快速上手 Umi。
# 一、UmiJS 简介
# 什么是 UmiJS
UmiJS(简称 Umi)是蚂蚁集团开源的企业级前端开发框架。它基于 React,提供开箱即用的开发体验,内置路由、构建、部署、测试等功能。
UmiJS 与 React 的关系:
- React 是 UI 库,专注于视图层
- UmiJS 是基于 React 的应用框架,提供完整的工程化解决方案
- 如果你还不熟悉 React,建议先阅读 React极简入门
# 核心特性
- 开箱即用:零配置即可开发,内置路由、构建、部署等功能
- 约定式路由:基于文件系统的路由,自动生成路由配置
- 插件化:强大的插件体系,易于扩展
- TypeScript 支持:原生支持 TypeScript
- 企业级:经过蚂蚁集团大规模业务验证
- 性能优化:内置 MFSU、预渲染等优化方案
# 为什么选择 UmiJS
| 特性 | UmiJS | Create React App | Next.js |
|---|---|---|---|
| 路由方案 | 约定式 + 配置式 | 需自行安装 | 文件系统路由 |
| 服务端渲染 | 支持(MFSU) | 不支持 | 支持(主要特性) |
| 插件体系 | 强大 | 基础 | 适中 |
| 企业级场景 | ✅ | ❌ | ✅ |
| 学习曲线 | 适中 | 简单 | 适中 |
| 中文文档 | 完善 | 一般 | 较好 |
# 适用场景
- 中后台系统:后台管理系统、数据大屏等
- 企业级应用:需要完整工程化方案的项目
- 多页应用:支持 MPA 模式
- 移动端 H5:配合 Ant Design Mobile
- 微前端:内置 qiankun 微前端方案
# 二、环境准备
# 开发环境要求
- Node.js 14 或更高版本(推荐 18.x)
- 包管理器:npm、yarn 或 pnpm
- 代码编辑器:推荐 VS Code
# 验证环境
node -v # 应该 >= 14.0.0
npm -v
# 创建第一个 Umi 项目
使用官方脚手架创建项目:
# 使用 pnpm(推荐)
pnpm create umi
# 或使用 npm
npx create-umi@latest
创建完成后:
cd my-umi-app
pnpm install
pnpm dev
浏览器访问 http://localhost:8000,看到欢迎页面表示成功。
# 项目结构
my-umi-app/
├── .umirc.ts # Umi 配置文件
├── package.json
├── tsconfig.json
├── src/
│ ├── .umi/ # Umi 临时文件(自动生成,不要修改)
│ ├── layouts/ # 全局布局
│ │ └── index.tsx
│ ├── pages/ # 页面目录(约定式路由)
│ │ ├── index.tsx # 首页 -> /
│ │ └── users/
│ │ └── index.tsx # 用户页 -> /users
│ ├── models/ # 全局数据流(dva)
│ ├── services/ # 接口请求
│ ├── components/ # 业务组件
│ └── app.ts # 运行时配置
└── mock/ # Mock 数据
# 三、核心概念
# 1. 约定式路由
Umi 会自动根据 pages 目录结构生成路由。
# 基础路由
src/pages/
├── index.tsx -> /
├── users.tsx -> /users
├── users/
│ ├── index.tsx -> /users
│ └── profile.tsx -> /users/profile
└── about.tsx -> /about
# 创建第一个页面
创建 src/pages/users/index.tsx:
import React from 'react';
export default function UsersPage() {
return (
<div>
<h1>用户列表</h1>
<ul>
<li>张三</li>
<li>李四</li>
<li>王五</li>
</ul>
</div>
);
}
访问 http://localhost:8000/users 即可看到页面。
# 动态路由
使用 [id] 表示动态参数:
src/pages/
└── users/
└── [id].tsx -> /users/1, /users/2, ...
src/pages/users/[id].tsx:
import { useParams } from 'umi';
export default function UserDetailPage() {
const params = useParams<{ id: string }>();
return (
<div>
<h1>用户详情</h1>
<p>用户 ID:{params.id}</p>
</div>
);
}
访问 http://localhost:8000/users/123,页面显示"用户 ID:123"。
# 嵌套路由
创建嵌套路由需要在父组件中使用 <Outlet />:
src/pages/users.tsx:
import { Outlet, Link } from 'umi';
export default function UsersLayout() {
return (
<div>
<h1>用户管理</h1>
<nav>
<Link to="/users">用户列表</Link> |
<Link to="/users/create">创建用户</Link>
</nav>
<hr />
<Outlet /> {/* 子路由渲染位置 */}
</div>
);
}
src/pages/users/index.tsx:
export default function UsersList() {
return <div>用户列表页面</div>;
}
src/pages/users/create.tsx:
export default function UsersCreate() {
return <div>创建用户页面</div>;
}
# 2. 路由配置
除了约定式路由,也支持配置式路由。在 .umirc.ts 中配置:
import { defineConfig } from 'umi';
export default defineConfig({
routes: [
{ path: '/', component: '@/pages/index' },
{
path: '/users',
component: '@/pages/users',
routes: [
{ path: '/users', component: '@/pages/users/index' },
{ path: '/users/:id', component: '@/pages/users/[id]' },
]
},
],
});
# 3. 导航
使用 Link 组件或 history API 进行导航。
# 使用 Link 组件
import { Link } from 'umi';
export default function HomePage() {
return (
<div>
<h1>首页</h1>
{/* 声明式导航 */}
<Link to="/users">查看用户</Link>
<Link to="/users/123">查看用户 123</Link>
</div>
);
}
# 使用 history API
import { history } from 'umi';
export default function LoginPage() {
const handleLogin = () => {
// 模拟登录
console.log('登录成功');
// 编程式导航
history.push('/dashboard');
// 或使用 replace(不会产生历史记录)
// history.replace('/dashboard');
};
return (
<div>
<h1>登录</h1>
<button onClick={handleLogin}>登录</button>
</div>
);
};
# 4. 布局
创建全局布局 src/layouts/index.tsx:
import { Link, Outlet } from 'umi';
import './index.less';
export default function Layout() {
return (
<div className="layout">
<header className="header">
<div className="logo">我的应用</div>
<nav>
<Link to="/">首页</Link>
<Link to="/users">用户</Link>
<Link to="/about">关于</Link>
</nav>
</header>
<main className="content">
<Outlet /> {/* 页面内容渲染位置 */}
</main>
<footer className="footer">
© 2024 我的应用
</footer>
</div>
);
}
样式文件 src/layouts/index.less:
.layout {
min-height: 100vh;
display: flex;
flex-direction: column;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 20px;
background: #001529;
color: white;
height: 64px;
.logo {
font-size: 20px;
font-weight: bold;
}
nav {
a {
color: rgba(255, 255, 255, 0.65);
margin-left: 20px;
text-decoration: none;
&:hover {
color: white;
}
}
}
}
.content {
flex: 1;
padding: 20px;
}
.footer {
text-align: center;
padding: 20px;
background: #f0f2f5;
}
# 5. 请求数据
Umi 推荐使用内置的 request 进行数据请求。
# 配置 request
在 src/app.ts 中配置全局请求设置:
import { RequestConfig } from 'umi';
export const request: RequestConfig = {
timeout: 10000,
errorConfig: {
adaptor: (resData) => {
return {
...resData,
success: resData.code === 0,
errorMessage: resData.message,
};
},
},
requestInterceptors: [
(url, options) => {
// 添加 token
const token = localStorage.getItem('token');
return {
url,
options: {
...options,
headers: {
...options.headers,
Authorization: token ? `Bearer ${token}` : '',
},
},
};
},
],
responseInterceptors: [
(response) => {
// 统一错误处理
return response;
},
],
};
# 创建 API 服务
src/services/user.ts:
import { request } from 'umi';
export interface User {
id: number;
name: string;
email: string;
age: number;
}
// 获取用户列表
export async function getUsers() {
return request<{ data: User[] }>('/api/users', {
method: 'GET',
});
}
// 获取用户详情
export async function getUser(id: number) {
return request<{ data: User }>(`/api/users/${id}`, {
method: 'GET',
});
}
// 创建用户
export async function createUser(data: Partial<User>) {
return request<{ data: User }>('/api/users', {
method: 'POST',
data,
});
}
// 更新用户
export async function updateUser(id: number, data: Partial<User>) {
return request<{ data: User }>(`/api/users/${id}`, {
method: 'PUT',
data,
});
}
// 删除用户
export async function deleteUser(id: number) {
return request(`/api/users/${id}`, {
method: 'DELETE',
});
}
# 在组件中使用
import React, { useState, useEffect } from 'react';
import { getUsers, User } from '@/services/user';
export default function UsersPage() {
const [users, setUsers] = useState<User[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchUsers();
}, []);
const fetchUsers = async () => {
try {
setLoading(true);
const response = await getUsers();
setUsers(response.data);
} catch (error) {
console.error('获取用户列表失败', error);
} finally {
setLoading(false);
}
};
if (loading) {
return <div>加载中...</div>;
}
return (
<div>
<h1>用户列表</h1>
<ul>
{users.map(user => (
<li key={user.id}>
{user.name} - {user.email}
</li>
))}
</ul>
</div>
);
}
# 6. Mock 数据
开发时可以使用 Mock 数据。创建 mock/users.ts:
import { defineMock } from 'umi';
export default defineMock({
// 获取用户列表
'GET /api/users': (req, res) => {
res.json({
code: 0,
message: 'success',
data: [
{ id: 1, name: '张三', email: 'zhangsan@example.com', age: 28 },
{ id: 2, name: '李四', email: 'lisi@example.com', age: 32 },
{ id: 3, name: '王五', email: 'wangwu@example.com', age: 25 },
],
});
},
// 获取用户详情
'GET /api/users/:id': (req, res) => {
const { id } = req.params;
res.json({
code: 0,
message: 'success',
data: {
id: Number(id),
name: `用户${id}`,
email: `user${id}@example.com`,
age: 20 + Number(id),
},
});
},
// 创建用户
'POST /api/users': (req, res) => {
res.json({
code: 0,
message: 'success',
data: {
id: Math.floor(Math.random() * 1000),
...req.body,
},
});
},
// 更新用户
'PUT /api/users/:id': (req, res) => {
res.json({
code: 0,
message: 'success',
data: {
id: Number(req.params.id),
...req.body,
},
});
},
// 删除用户
'DELETE /api/users/:id': (req, res) => {
res.json({
code: 0,
message: 'success',
});
},
});
# 7. 状态管理
Umi 内置了简化版的 dva,支持全局状态管理。
# 创建 Model
src/models/user.ts:
import { useState } from 'react';
import { getUsers, User } from '@/services/user';
export default function useUserModel() {
const [users, setUsers] = useState<User[]>([]);
const [loading, setLoading] = useState(false);
const fetchUsers = async () => {
setLoading(true);
try {
const response = await getUsers();
setUsers(response.data);
} catch (error) {
console.error('获取用户失败', error);
} finally {
setLoading(false);
}
};
const addUser = (user: User) => {
setUsers([...users, user]);
};
const removeUser = (id: number) => {
setUsers(users.filter(u => u.id !== id));
};
return {
users,
loading,
fetchUsers,
addUser,
removeUser,
};
}
# 在组件中使用
import { useModel } from 'umi';
import { useEffect } from 'react';
export default function UsersPage() {
const { users, loading, fetchUsers, removeUser } = useModel('user');
useEffect(() => {
fetchUsers();
}, []);
const handleDelete = (id: number) => {
if (confirm('确定删除吗?')) {
removeUser(id);
}
};
if (loading) {
return <div>加载中...</div>;
}
return (
<div>
<h1>用户列表</h1>
<table>
<thead>
<tr>
<th>ID</th>
<th>姓名</th>
<th>邮箱</th>
<th>年龄</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{users.map(user => (
<tr key={user.id}>
<td>{user.id}</td>
<td>{user.name}</td>
<td>{user.email}</td>
<td>{user.age}</td>
<td>
<button onClick={() => handleDelete(user.id)}>删除</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
);
}
# 四、实战示例:用户管理系统
# 1. 项目结构
src/
├── layouts/
│ └── index.tsx # 全局布局
├── pages/
│ ├── index.tsx # 首页
│ └── users/
│ ├── index.tsx # 用户列表
│ ├── create.tsx # 创建用户
│ └── [id].tsx # 用户详情
├── models/
│ └── user.ts # 用户 Model
├── services/
│ └── user.ts # 用户 API
└── components/
└── UserForm.tsx # 用户表单组件
# 2. 用户列表页面
src/pages/users/index.tsx:
import { useModel, history } from 'umi';
import { useEffect } from 'react';
import './index.less';
export default function UsersPage() {
const { users, loading, fetchUsers, removeUser } = useModel('user');
useEffect(() => {
fetchUsers();
}, []);
const handleDelete = async (id: number) => {
if (confirm('确定删除吗?')) {
removeUser(id);
}
};
const handleEdit = (id: number) => {
history.push(`/users/${id}`);
};
if (loading) {
return <div className="loading">加载中...</div>;
}
return (
<div className="users-page">
<div className="header">
<h1>用户管理</h1>
<button
className="btn-primary"
onClick={() => history.push('/users/create')}
>
创建用户
</button>
</div>
<table className="users-table">
<thead>
<tr>
<th>ID</th>
<th>姓名</th>
<th>邮箱</th>
<th>年龄</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{users.map(user => (
<tr key={user.id}>
<td>{user.id}</td>
<td>{user.name}</td>
<td>{user.email}</td>
<td>{user.age}</td>
<td>
<button
className="btn-link"
onClick={() => handleEdit(user.id)}
>
编辑
</button>
<button
className="btn-link btn-danger"
onClick={() => handleDelete(user.id)}
>
删除
</button>
</td>
</tr>
))}
</tbody>
</table>
{users.length === 0 && (
<div className="empty">暂无数据</div>
)}
</div>
);
}
样式 src/pages/users/index.less:
.users-page {
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
h1 {
margin: 0;
}
}
.users-table {
width: 100%;
border-collapse: collapse;
th, td {
padding: 12px;
text-align: left;
border-bottom: 1px solid #e8e8e8;
}
th {
background: #fafafa;
font-weight: 600;
}
tbody tr:hover {
background: #fafafa;
}
}
.btn-primary {
padding: 8px 16px;
background: #1890ff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
&:hover {
background: #40a9ff;
}
}
.btn-link {
padding: 4px 8px;
margin-right: 8px;
background: transparent;
color: #1890ff;
border: none;
cursor: pointer;
&:hover {
color: #40a9ff;
}
&.btn-danger {
color: #ff4d4f;
&:hover {
color: #ff7875;
}
}
}
.loading, .empty {
text-align: center;
padding: 40px;
color: #999;
}
}
# 3. 创建用户页面
src/pages/users/create.tsx:
import { useState } from 'react';
import { history, useModel } from 'umi';
import { createUser } from '@/services/user';
import './form.less';
export default function CreateUserPage() {
const { addUser } = useModel('user');
const [loading, setLoading] = useState(false);
const [formData, setFormData] = useState({
name: '',
email: '',
age: 0,
});
const handleChange = (field: string, value: any) => {
setFormData({ ...formData, [field]: value });
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!formData.name || !formData.email) {
alert('请填写完整信息');
return;
}
try {
setLoading(true);
const response = await createUser(formData);
addUser(response.data);
alert('创建成功');
history.push('/users');
} catch (error) {
alert('创建失败');
} finally {
setLoading(false);
}
};
return (
<div className="user-form-page">
<h1>创建用户</h1>
<form onSubmit={handleSubmit} className="user-form">
<div className="form-item">
<label>姓名:</label>
<input
type="text"
value={formData.name}
onChange={(e) => handleChange('name', e.target.value)}
placeholder="请输入姓名"
/>
</div>
<div className="form-item">
<label>邮箱:</label>
<input
type="email"
value={formData.email}
onChange={(e) => handleChange('email', e.target.value)}
placeholder="请输入邮箱"
/>
</div>
<div className="form-item">
<label>年龄:</label>
<input
type="number"
value={formData.age}
onChange={(e) => handleChange('age', Number(e.target.value))}
placeholder="请输入年龄"
/>
</div>
<div className="form-actions">
<button type="submit" disabled={loading}>
{loading ? '提交中...' : '提交'}
</button>
<button type="button" onClick={() => history.back()}>
取消
</button>
</div>
</form>
</div>
);
}
样式 src/pages/users/form.less:
.user-form-page {
max-width: 600px;
h1 {
margin-bottom: 24px;
}
.user-form {
.form-item {
margin-bottom: 20px;
label {
display: block;
margin-bottom: 8px;
font-weight: 500;
}
input {
width: 100%;
padding: 8px 12px;
border: 1px solid #d9d9d9;
border-radius: 4px;
font-size: 14px;
&:focus {
outline: none;
border-color: #1890ff;
}
}
}
.form-actions {
margin-top: 24px;
display: flex;
gap: 12px;
button {
padding: 8px 24px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
&[type="submit"] {
background: #1890ff;
color: white;
&:hover:not(:disabled) {
background: #40a9ff;
}
&:disabled {
opacity: 0.5;
cursor: not-allowed;
}
}
&[type="button"] {
background: #f0f0f0;
color: #333;
&:hover {
background: #e0e0e0;
}
}
}
}
}
}
# 五、常用配置
# 1. 基础配置
.umirc.ts:
import { defineConfig } from 'umi';
export default defineConfig({
// 标题
title: '我的应用',
// 部署路径
base: '/',
publicPath: '/',
// 输出目录
outputPath: 'dist',
// 代理配置
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true,
},
},
// 路由模式
history: { type: 'hash' }, // 或 'browser'
// 开启 hash 文件名
hash: true,
// 压缩代码
jsMinifier: 'esbuild',
cssMinifier: 'esbuild',
});
# 2. 插件配置
export default defineConfig({
plugins: [
'@umijs/plugins/dist/initial-state',
'@umijs/plugins/dist/model',
'@umijs/plugins/dist/request',
],
// Ant Design 配置
antd: {},
// 初始化数据
initialState: {},
// Model 插件配置
model: {},
});
# 3. 环境变量
创建 .env 文件:
# 开发环境
UMI_ENV=dev
API_URL=http://localhost:3000
创建 .env.production:
# 生产环境
UMI_ENV=prod
API_URL=https://api.example.com
在代码中使用:
const apiUrl = process.env.API_URL;
# 六、最佳实践
# 1. 目录组织
src/
├── components/ # 通用组件
│ ├── Button/
│ ├── Card/
│ └── Table/
├── pages/ # 页面
│ ├── users/
│ └── dashboard/
├── models/ # 数据模型
├── services/ # API 服务
├── utils/ # 工具函数
│ ├── request.ts
│ └── format.ts
├── constants/ # 常量
│ └── index.ts
└── types/ # TypeScript 类型
└── user.ts
# 2. 代码分割
使用动态导入实现按需加载:
import { lazy } from 'react';
const UsersPage = lazy(() => import('./pages/users'));
# 3. 权限控制
在 src/app.tsx 中配置:
import { RunTimeLayoutConfig } from 'umi';
export const layout: RunTimeLayoutConfig = () => {
return {
// 权限配置
rightRender: () => <div>用户信息</div>,
onPageChange: () => {
// 页面切换时检查权限
const token = localStorage.getItem('token');
if (!token && location.pathname !== '/login') {
history.push('/login');
}
},
};
};
# 4. 错误处理
创建全局错误边界:
import React from 'react';
export default class ErrorBoundary extends React.Component {
state = { hasError: false };
static getDerivedStateFromError(error: any) {
return { hasError: true };
}
componentDidCatch(error: any, errorInfo: any) {
console.error('错误:', error, errorInfo);
}
render() {
if (this.state.hasError) {
return <div>出错了!</div>;
}
return this.props.children;
}
}
# 七、集成 Ant Design
# 1. 安装
Umi 4 已内置 Ant Design 5,开启即可:
.umirc.ts:
export default defineConfig({
antd: {},
});
# 2. 使用组件
import { Button, Table, Form, Input, message } from 'antd';
import type { ColumnsType } from 'antd/es/table';
import { User } from '@/services/user';
export default function UsersPage() {
const columns: ColumnsType<User> = [
{
title: 'ID',
dataIndex: 'id',
key: 'id',
},
{
title: '姓名',
dataIndex: 'name',
key: 'name',
},
{
title: '邮箱',
dataIndex: 'email',
key: 'email',
},
{
title: '年龄',
dataIndex: 'age',
key: 'age',
},
{
title: '操作',
key: 'action',
render: (_, record) => (
<Button type="link" onClick={() => handleEdit(record.id)}>
编辑
</Button>
),
},
];
const handleEdit = (id: number) => {
message.success('编辑用户:' + id);
};
return (
<div>
<Table columns={columns} dataSource={[]} rowKey="id" />
</div>
);
}
# 八、构建和部署
# 1. 构建
# 生产环境构建
pnpm build
# 指定环境构建
UMI_ENV=test pnpm build
构建后的文件在 dist 目录。
# 2. 本地预览
pnpm preview
# 3. 部署
# 部署到 Nginx
server {
listen 80;
server_name example.com;
root /var/www/dist;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
}
# 部署到 CDN
在 .umirc.ts 中配置:
export default defineConfig({
publicPath: 'https://cdn.example.com/',
});
# 九、学习资源
# 官方文档
# 推荐阅读
- Ant Design Pro:基于 Umi + Ant Design 的企业级中后台解决方案
- dumi:基于 Umi 的组件文档工具
- qiankun:Umi 内置的微前端方案
# 进阶主题
- 微前端架构
- 性能优化(MFSU)
- 服务端渲染(SSR)
- 自定义插件开发
- 国际化(i18n)
- 主题定制
# 十、总结
UmiJS 的核心特性:
- 约定式路由:基于文件系统自动生成路由
- 开箱即用:内置构建、部署、测试等功能
- 插件化:强大的插件体系
- 企业级:经过大规模业务验证
开始使用 UmiJS 的步骤:
- 创建项目:
pnpm create umi - 熟悉约定式路由
- 掌握数据请求和状态管理
- 集成 Ant Design
- 实践完整项目
UmiJS 特别适合构建中后台系统和企业级应用,它的约定式路由和插件化设计能够大幅提升开发效率!
祝你变得更强!