Umi 入门实战:从零创建 Hello World 工程
- [Umi 入门实战:从零创建 Hello World 工程](#Umi 入门实战:从零创建 Hello World 工程)
-
- 目标
- 第一步:创建项目目录
- [第二步:初始化 npm 项目](#第二步:初始化 npm 项目)
- [第三步:安装 Umi 依赖](#第三步:安装 Umi 依赖)
- 第四步:创建源码目录结构
- [第五步:添加 npm 脚本](#第五步:添加 npm 脚本)
- 第六步:启动开发服务器
- 第七步:浏览器访问
- 第八步:修改代码体验热更新
- 第九步:添加第二个页面
- 第十步:添加页面导航
- 第十一步:添加全局样式
- 第十二步:添加布局组件
- 第十三步:构建生产版本
- 第十四步:预览生产版本
- 完整项目结构
- 核心概念总结
- 下一步学习
Umi 入门实战:从零创建 Hello World 工程
目标
创建一个最简单的 Umi 工程,显示 "Hello World",理解每一步的意义。
第一步:创建项目目录
操作
bash
# 创建项目目录
mkdir hello-umi
# 进入目录
cd hello-umi
意义
hello-umi/ ← 项目根目录
← 目前为空,接下来逐步填充
为什么需要单独的目录?
- Node.js 项目需要独立的目录来管理依赖和配置
- 目录名通常作为项目名称
- 隔离不同项目的依赖,避免冲突
第二步:初始化 npm 项目
操作
bash
npm init -y
输出结果
json
{
"name": "hello-umi",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {},
"keywords": [],
"author": "",
"license": "ISC"
}
意义
生成 package.json 文件,这是 Node.js 项目的"身份证":
| 字段 | 含义 |
|---|---|
name |
项目名称,发布 npm 包时必须唯一 |
version |
版本号,遵循语义化版本规范 |
scripts |
脚本命令,如 npm run dev |
dependencies |
运行时依赖 |
devDependencies |
开发时依赖 |
为什么用 -y?
- 跳过交互式问答,使用默认值
- 快速初始化,后续可手动修改
第三步:安装 Umi 依赖
操作
bash
npm install umi @umijs/preset-umi --save-dev
等待安装完成后,package.json 变化
json
{
"devDependencies": {
"umi": "^4.0.0",
"@umijs/preset-umi": "^4.0.0"
}
}
意义
| 包名 | 作用 |
|---|---|
umi |
Umi 核心包,提供 CLI 命令和 API |
@umijs/preset-umi |
Umi 预设插件集,包含路由、构建等核心功能 |
为什么用 --save-dev?
- Umi 是构建工具,只在开发时使用
- 生产环境只需要构建产物,不需要 Umi 本身
- 区分
dependencies(运行时)和devDependencies(开发时)
依赖安装到哪里了?
hello-umi/
├── node_modules/ ← 依赖包存放位置
│ ├── umi/
│ ├── @umijs/
│ └── ...(数百个子依赖)
├── package.json
└── package-lock.json ← 锁定依赖版本
第四步:创建源码目录结构
操作
bash
# 创建 src 目录
mkdir src
# 创建 pages 目录
mkdir src/pages
# 创建首页文件
创建 src/pages/index.tsx
tsx
// src/pages/index.tsx
export default function HomePage() {
return (
<div>
<h1>Hello World</h1>
<p>这是我的第一个 Umi 应用</p>
</div>
);
}
目录结构
hello-umi/
├── node_modules/
├── src/
│ └── pages/
│ └── index.tsx ← 首页组件
├── package.json
└── package-lock.json
意义
| 目录/文件 | 意义 |
|---|---|
src/ |
源码目录,所有业务代码放这里 |
src/pages/ |
页面目录,Umi 约定的路由目录 |
index.tsx |
首页,文件名 index 对应路由 / |
约定式路由规则:
pages/index.tsx → 路由 /
pages/users.tsx → 路由 /users
pages/users/[id].tsx → 路由 /users/:id
第五步:添加 npm 脚本
操作
修改 package.json,添加 scripts:
json
{
"name": "hello-umi",
"version": "1.0.0",
"scripts": {
"dev": "umi dev",
"build": "umi build",
"preview": "umi preview"
},
"devDependencies": {
"umi": "^4.0.0",
"@umijs/preset-umi": "^4.0.0"
}
}
意义
| 脚本 | 命令 | 作用 |
|---|---|---|
dev |
umi dev |
启动开发服务器,支持热更新 |
build |
umi build |
构建生产环境代码 |
preview |
umi preview |
预览构建后的产物 |
为什么不直接运行 umi dev?
- 需要
npx umi dev或./node_modules/.bin/umi dev - npm scripts 会自动将
node_modules/.bin加入 PATH - 更简洁:
npm run dev
第六步:启动开发服务器
操作
bash
npm run dev
终端输出
info - Umi v4.0.0
info - Preparing...
info - Generate files
event - Start generating files
event - Generate files done
info - Webpack compiling...
event - First compile done
Ready in 1234 ms
➜ Local: http://localhost:8000/
➜ Network: http://192.168.1.100:8000/
此时发生了什么?
┌─────────────────────────────────────────────────────────────┐
│ 1. Umi 读取配置(无配置文件,使用默认值) │
│ ↓ │
│ 2. 扫描 src/pages/ 目录,生成路由配置 │
│ 发现 pages/index.tsx → 生成路由 { path: '/', component: ... }│
│ ↓ │
│ 3. 生成临时文件到 .umi/ 目录 │
│ ├── .umi/umi.ts ← 入口文件 │
│ ├── .umi/core/route.tsx ← 路由配置 │
│ └── .umi/core/plugin.ts ← 插件系统 │
│ ↓ │
│ 4. 启动 Webpack Dev Server │
│ ├── 监听文件变化 │
│ ├── 编译 TypeScript │
│ └── 提供 HMR(热更新) │
│ ↓ │
│ 5. 打开浏览器 http://localhost:8000 │
└─────────────────────────────────────────────────────────────┘
自动生成的 .umi 目录
src/.umi/
├── core/
│ ├── route.tsx ← 路由配置
│ ├── history.ts ← 路由历史
│ └── plugin.ts ← 插件运行时
├── umi.ts ← 真正的入口文件
└── exports.ts ← umi API 导出
查看生成的入口文件:
typescript
// src/.umi/umi.ts(简化版)
import './core/polyfill';
import { renderClient } from 'umi';
import { routes } from './core/route';
renderClient({
routes,
rootElement: document.getElementById('root'),
});
查看生成的路由配置:
typescript
// src/.umi/core/route.tsx(简化版)
export const routes = [
{
path: '/',
element: require('@/pages/index').default,
},
];
第七步:浏览器访问
操作
打开浏览器,访问 http://localhost:8000
页面显示
┌─────────────────────────────────┐
│ │
│ Hello World │
│ │
│ 这是我的第一个 Umi 应用 │
│ │
└─────────────────────────────────┘
渲染流程
┌─────────────────────────────────────────────────────────────┐
│ 1. 浏览器加载 HTML │
│ <div id="root"></div> │
│ ↓ │
│ 2. 加载 umi.ts 入口文件 │
│ ↓ │
│ 3. 执行 renderClient() │
│ ↓ │
│ 4. 根据当前 URL 匹配路由 │
│ URL: / → 匹配到 pages/index.tsx │
│ ↓ │
│ 5. 渲染组件到 #root │
│ ReactDOM.render(<HomePage />, document.getElementById('root'))│
└─────────────────────────────────────────────────────────────┘
第八步:修改代码体验热更新
操作
修改 src/pages/index.tsx:
tsx
// src/pages/index.tsx
export default function HomePage() {
return (
<div>
<h1>Hello Umi!</h1>
<p>我修改了代码,页面自动更新了!</p>
<p>当前时间:{new Date().toLocaleString()}</p>
</div>
);
}
观察
保存文件后,浏览器自动刷新,显示新内容。
热更新原理
┌─────────────────────────────────────────────────────────────┐
│ 1. Webpack 监听文件变化 │
│ ↓ │
│ 2. 检测到 index.tsx 变化 │
│ ↓ │
│ 3. 重新编译该模块 │
│ ↓ │
│ 4. 通过 WebSocket 通知浏览器 │
│ ↓ │
│ 5. 浏览器接收更新,执行 HMR │
│ ↓ │
│ 6. 页面局部更新(无需完整刷新) │
└─────────────────────────────────────────────────────────────┘
第九步:添加第二个页面
操作
创建 src/pages/about.tsx:
tsx
// src/pages/about.tsx
export default function AboutPage() {
return (
<div>
<h1>关于我们</h1>
<p>这是关于页面</p>
</div>
);
}
访问
打开 http://localhost:8000/about
发生了什么?
┌─────────────────────────────────────────────────────────────┐
│ 1. Umi 检测到新文件 pages/about.tsx │
│ ↓ │
│ 2. 重新生成路由配置 │
│ routes = [ │
│ { path: '/', element: Index }, │
│ { path: '/about', element: About }, ← 新增 │
│ ] │
│ ↓ │
│ 3. 浏览器访问 /about │
│ ↓ │
│ 4. 路由匹配,渲染 AboutPage 组件 │
└─────────────────────────────────────────────────────────────┘
无需任何配置,新页面自动生效! 这就是"约定优于配置"。
第十步:添加页面导航
操作
修改 src/pages/index.tsx:
tsx
// src/pages/index.tsx
import { Link } from 'umi';
export default function HomePage() {
return (
<div>
<h1>Hello Umi!</h1>
<p>这是首页</p>
{/* 导航链接 */}
<nav>
<Link to="/">首页</Link>
{' | '}
<Link to="/about">关于</Link>
</nav>
</div>
);
}
意义
tsx
import { Link } from 'umi';
Link是 Umi 提供的路由组件- 类似 HTML 的
<a>标签,但不会触发页面刷新 - 使用路由跳转,体验更流畅
tsx
<Link to="/about">关于</Link>
- 渲染为
<a href="/about">关于</a> - 点击时通过 JavaScript 路由跳转
- 不重新加载页面,只更新组件
第十一步:添加全局样式
操作
创建 src/global.css:
css
/* src/global.css */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #f5f5f5;
padding: 20px;
}
nav a {
color: #3370ff;
text-decoration: none;
margin: 0 8px;
}
nav a:hover {
text-decoration: underline;
}
意义
global.css是 Umi 约定的全局样式文件- 自动在入口文件中引入
- 影响所有页面
查看生成的入口文件:
typescript
// src/.umi/umi.ts(简化版)
import '../global.css'; // ← 自动引入
import { renderClient } from 'umi';
// ...
第十二步:添加布局组件
操作
创建 src/layouts/index.tsx:
tsx
// src/layouts/index.tsx
import { Outlet, Link } from 'umi';
export default function Layout() {
return (
<div style={{ maxWidth: 800, margin: '0 auto' }}>
{/* 顶部导航 */}
<header style={{
padding: '16px',
background: '#fff',
marginBottom: '16px',
borderRadius: '8px'
}}>
<nav>
<Link to="/">首页</Link>
{' | '}
<Link to="/about">关于</Link>
</nav>
</header>
{/* 页面内容 */}
<main style={{
padding: '16px',
background: '#fff',
borderRadius: '8px'
}}>
<Outlet />
</main>
{/* 底部 */}
<footer style={{
marginTop: '16px',
textAlign: 'center',
color: '#999'
}}>
© 2024 Hello Umi
</footer>
</div>
);
}
意义
┌─────────────────────────────────────────────────────────────┐
│ layouts/index.tsx 是全局布局 │
│ │
│ ┌─────────────────────────────────────┐ │
│ │ header(导航) │ │
│ ├─────────────────────────────────────┤ │
│ │ │ │
│ │ main │ │
│ │ ┌─────────────────────────────┐ │ │
│ │ │ <Outlet /> │ │ ← 页面内容渲染这里 │
│ │ │ │ │ │
│ │ │ pages/index.tsx 或 │ │ │
│ │ │ pages/about.tsx │ │ │
│ │ └─────────────────────────────┘ │ │
│ │ │ │
│ ├─────────────────────────────────────┤ │
│ │ footer │ │
│ └─────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
<Outlet /> 是什么?
- React Router 提供的组件
- 表示"子路由渲染的位置"
- 所有页面内容都会显示在这里
第十三步:构建生产版本
操作
bash
npm run build
终端输出
info - Umi v4.0.0
info - Build for production
info - Generate files
info - Webpack compiling...
√ Webpack: Compiled successfully in 5.67s
info - File sizes after gzip:
12.34 kB dist/umi.js
1.23 kB dist/index.html
event - Build index.html
生成的 dist 目录
dist/
├── index.html ← 入口 HTML
├── umi.[hash].js ← 主 JS 包
├── umi.[hash].css ← 主 CSS 包
└── static/ ← 静态资源
构建流程
┌─────────────────────────────────────────────────────────────┐
│ 1. 清理 dist/ 目录 │
│ ↓ │
│ 2. 生成 .umi-production/ 临时文件 │
│ ↓ │
│ 3. Webpack 生产模式编译 │
│ ├── Tree Shaking(移除未使用代码) │
│ ├── 代码压缩(Terser) │
│ ├── CSS 压缩(cssnano) │
│ └── 文件名加 hash(缓存控制) │
│ ↓ │
│ 4. 生成 index.html │
│ ↓ │
│ 5. 输出到 dist/ │
└─────────────────────────────────────────────────────────────┘
第十四步:预览生产版本
操作
bash
npm run preview
访问
意义
- 使用生产构建启动服务器
- 验证构建产物是否正确
- 模拟生产环境运行
完整项目结构
hello-umi/
├── dist/ ← 构建产物(git 忽略)
│ ├── index.html
│ ├── umi.[hash].js
│ └── umi.[hash].css
│
├── node_modules/ ← 依赖包(git 忽略)
│
├── src/
│ ├── .umi/ ← 临时文件(自动生成,git 忽略)
│ │ ├── core/
│ │ │ ├── route.tsx
│ │ │ └── plugin.ts
│ │ └── umi.ts
│ │
│ ├── layouts/ ← 布局目录
│ │ └── index.tsx
│ │
│ ├── pages/ ← 页面目录
│ │ ├── index.tsx ← 首页 /
│ │ └── about.tsx ← 关于页 /about
│ │
│ └── global.css ← 全局样式
│
├── package.json ← 项目配置
├── package-lock.json ← 依赖锁定
└── .gitignore ← Git 忽略配置
核心概念总结
约定式路由
| 文件路径 | 生成的路由 |
|---|---|
pages/index.tsx |
/ |
pages/about.tsx |
/about |
pages/users/[id].tsx |
/users/:id |
pages/users/index.tsx |
/users |
核心文件
| 文件 | 作用 |
|---|---|
pages/*.tsx |
页面组件,自动生成路由 |
layouts/index.tsx |
全局布局,包裹所有页面 |
global.css |
全局样式,自动引入 |
.umi/umi.ts |
入口文件,自动生成 |
npm scripts
| 命令 | 作用 |
|---|---|
npm run dev |
开发模式,热更新 |
npm run build |
生产构建 |
npm run preview |
预览构建产物 |
下一步学习
- 添加配置文件
.umirc.ts- 自定义配置 - 添加运行时配置
app.tsx- 请求拦截、路由钩子 - 使用插件 - antd、request、model 等
- CSS Modules - 组件级样式隔离
- 数据流 - useModel 状态管理
文档生成时间:2026-06-04