一、umi简介
Umi,中文发音为「乌米」,是可扩展的企业级前端应用框架。Umi 以路由为基础的,同时支持配置式路由和约定式路由,保证路由的功能完备,并以此进行功能扩展。然后配以生命周期完善的插件体系,覆盖从源码到构建产物的每个生命周期,支持各种功能扩展和业务需求。
Umi 是蚂蚁集团的底层前端框架,已直接或间接地服务了 10000+ 应用,包括 Java、Node、H5 无线、离线(Hybrid)应用、纯前端 assets 应用、CMS 应用、Electron 应用、Serverless 应用等。他已经很好地服务了我们的内部用户,同时也服务了不少外部用户,包括淘系、飞猪、阿里云、字节、腾讯、口碑、美团等。在 2021 年字节的调研报告中,Umi 是其中 25.33% 开发者的选择。
Umi 有很多非常有意思的特性,比如。
1、企业级,在安全性、稳定性、最佳实践、约束能力方面会考虑更多 2、插件化,啥都能改,Umi 本身也是由插件构成 3、MFSU,比 Vite 还快的 Webpack 打包方案 4、基于 React Router 6 的完备路由 5、默认最快的请求 6、SSR & SSG 7、稳定白盒性能好的 ESLint 和 Jest 8、React 18 的框架级接入 9、Monorepo 最佳实践
官网地址:https://umijs.org/
二、umi环境搭建
1、快速上手
第1步、创建umi项目
首先在终端执行如下命令来创建umi项目
yarn create umi
第2步、项目模板选择
-
simple App:你可以理解为纯净版的Umi,包含umi4文档上关于Guides下的所有功能,但是不包括Umi Max。
-
Ant Design Pro:包含Umi Max的完整功能,均可通过在.umirc.ts 或 config/config.ts中插拔式配置。
-
Vue Simple App:如果你的项目框架是vue则使用该模版。
第3步、选择包管理工具
? Pick Npm Client » - Use arrow-keys. Return to submit.
npm
cnpm
tnpm
> yarn
pnpm
第4步、选择资源注册地
? Pick Npm Registry » - Use arrow-keys. Return to submit.
npm
> taobao
第5步、启动项目
yarn dev
第6步、运行项目
在浏览器地址栏输入:http://localhost:8000
2、目录结构
.
├── config
│ └── config.ts
├── dist
├── mock
│ └── app.ts|tsx
├── src
│ ├── .umi
│ ├── .umi-production
│ ├── app.ts
│ ├── layouts
│ │ ├── BasicLayout.tsx
│ │ ├── index.less
│ ├── models
│ │ ├── global.ts
│ │ └── index.ts
│ ├── pages
│ │ ├── index.less
│ │ └── index.tsx
│ ├── utils // 推荐目录
│ │ └── index.ts
│ ├── services // 推荐目录
│ │ └── api.ts
│ ├── global.ts
│ ├── global.(css|less|sass|scss)
│ ├── favicon.(ico|gif|png|jpg|jpeg|svg|avif|webp)
│ └── loading.tsx
├── node_modules
│ └── .cache
│ ├── bundler-webpack
│ ├── mfsu
│ └── mfsu-deps
├── .env
├── plugin.ts
├── .umirc.ts // 与 config/config 文件 2 选一
├── package.json
├── tsconfig.json
└── typings.d.ts
-
.umi:dev 临时目录,需添加到 .gitignore
-
.umirc.ts:配置文件,包含 Umi 内置功能和插件的配置。与
config/config.ts
文件功能相同,2 选 1 。.umirc.ts
文件优先级较高 -
config/config.ts:配置文件,包含 Umi 内置功能和插件的配置。与
config/config.ts
文件功能相同,2 选 1 。.umirc.ts
文件优先级较高 -
dist:执行
umi build
后,产物默认会存放在这里。可通过配置修改产物输出路径 -
.env:环境变量,用户自己创建的
-
mock:存储 mock 文件,此目录下所有
js
和ts
文件会被解析为 mock 文件。用于本地的模拟数据服务。 -
public:此目录下所有文件会被 copy 到输出路径。
-
tsconfig.json:ts的配置文件
3、常用配置
3.1、typescript提示
如果想要在配置时也有 Typescript 的语法提示,可以在配置的地方包一层 defineConfig
, 这样配置的时候就可以有语法提示了:
//umirc.ts
import {defineConfig} from 'umi'
export default defineConfig({
npmClient: 'yarn',
});
3.2、常见配置设置
//umirc.ts
import {defineConfig} from 'umi'
export default defineConfig({
npmClient: 'yarn',
title:'Hello UMI4',//配置标题
favicons:['/favicon.ico'], //配置favicon使用本地图片,图片放在public目录下
});
3.2、环境变量配置
在项目根目录下的.env目录下配置,常见环境变量配置有修改服务器的端口号。
//.env
port=4000
三、路由配置
Umi 中关于路由的配置,有两种方式:
-
配置式路由:在配置文件
.umirc.ts
中通过代码对路由进行相关的配置; -
约定式路由:不需要通过代码去手写路由配置,只需要按照 Umi 的约定去创建项目页面的目录,Umi 会自动解析出路由的配置;
在一个项目中,配置式路由和约定式路由只能任选其一。
1、配置式路由(掌握)
1.1、配置一二级路由
在配置文件(.umirc.ts
)中通过 routes
进行配置,格式为路由信息的数组。
import { defineConfig } from 'umi';
export default defineConfig({
routes: [
{ path: '/login', component: '@/pages/login'},
{ path:'/register',component:'@/pages/register'},
{
path: '/', component: '@/pages/index',
routes: [
{ path: '/student', component: '@/pages/student'},
{ path: '/director', component: '@/pages/director' }
]
}
]
});
1.2、配置路由出口
然后在 src/layouts/index
中通过 <Outlet/>
渲染子路由,配置一级路由出口
import {Outlet } from 'umi';
export default function Layout() {
return (
<div>
<Outlet />
</div>
);
}
然后再在src/pages/index
中配置二级路由的出口
import {Outlet} from 'umi'
import './index.less'
export default function index() {
return (
<div className='box'>
<div className='navs'>
<ul>
<li>学生管理</li>
<li>班主任管理</li>
</ul>
</div>
<div className='main'>
<Outlet></Outlet>
</div>
</div>
)
}
1.3、配置路由模式
Umi 项目中,路由默认history 模式,如果要更改为 hash 模式,可以在配置文件 .umirc.ts
中添加以下属性:
export default defineConfig({
history: {
type: 'hash'
},
// ...
});
2、路由跳转
Umi 中,将路由的跳转方式分为“声明式”和“命令式”,实际上对应的就是标签跳转和事件跳转。
2.1、声明式
通过<Link>
组件方式进行跳转
import {Link, Outlet} from 'umi'
import './index.less'
export default function index() {
return (
<div className='box'>
<div className='navs'>
<ul>
<li><Link to="/student">学生管理</Link></li>
<li><Link to="/direct">班主任管理</Link></li>
</ul>
</div>
<div className='main'>
<Outlet></Outlet>
</div>
</div>
)
}
2.2、命令式
通过 history 使用,通常在事件处理中被调用
import {history} from 'umi'
export default function login() {
const toRegister=(e:any)=>{
e.preventDefault()
history.push('/register')
}
return (
<div>
<h1>登录</h1>
<a href="#" onClick={toRegister}>没有账号,请注册</a>
</div>
)
}
3、路由传参
3.1、query传参
-
跳转时传递参数
<Link to="/路径?参数名=参数值">跳转</Link>
history.push('/路径?参数名=参数值');
-
组件中接收参数
import {useSearchParams} from 'umi'
const[searchParams,setSearchParams]=useSearchParams()
searchParams.get(参数名)
3.2、动态路由传参
-
跳转时传递参数
history.push('/路径/参数')
-
配置动态路由
在 .umirc.ts
文件中,更改动态路由的配置:
{ path: '/路径/:变量名', component: '组件路径' },
-
在组件中获取参数
import { useParams } from 'umi';
const params = useParams();
4、路由的其他配置
4.1、路由重定向
[
{
path:'/',redirect:'/home'
}
]
4.2、404页面配置
在umi中,只需要在所有路由配置的最后,添加一个关于404页面的配置
[
{
path:'*',
component:'@/pages/notfound'
}
]
四、Mock数据和网络请求
Mock数据是前后端开发过程中必不可少的一环,是前后端开发的关键链路,由于前后端开发过程中前端要使用到后端的数据,但是如果后端api没有开发出来,前端不会要等后端API才能开发,这样在开中会调职前端工作的阻塞,为了解决这个问题可以采用Mock数据的方式。
什么是 Mock 数据:在前后端约定好 API 接口以后,前端可以使用 Mock 数据来在本地模拟出 API 应该要返回的数据,这样一来前后端开发就可以同时进行,不会因为后端 API 还在开发而导致前端的工作被阻塞。
Umi 提供了开箱即用的 Mock 功能,能够用方便简单的方式来完成 Mock 数据的设置。
1、目录约定
Umi 约定 /mock
目录下的所有文件为 Mock 文件,例如这样的目录结构
.
├── mock
├── todos.js
├── items.js
└── users.js
└── src
└── pages
└── index.js
2、Mock文件
Mock 文件默认导出一个对象,而对象的每个 Key 对应了一个 Mock 接口,值则是这个接口所对应的返回数据,例如这样的 Mock 文件:
export default{
'GET /api/goods':[
{id:'1001',name:'曲奇饼干',price:23.5},
{id:'1002',name:'德芙巧克力',price:63.2},
{id:'1003',name:'大列巴面包',price:13.5}
]
}
当 Http 的请求方法是 GET 时,可以省略方法部分,只需要路径即可
3、自定义函数
除了直接静态声明返回值,也可以用函数的方式来声明如何计算返回值
export default {
'POST /api/login': (req, res) => {
const { username, password } = req.body
if (username == "Giles" && password == "123456") {
res.send({
code: 1,
message: '登录成功'
})
} else {
res.send({
code: 0,
message: '登录失败'
})
}
}
}
4、引入Mock.js
在 Mock 中我们经常使用 Mock.js 来帮我们方便的生成随机的模拟数据,如果你使用了 Umi 的 Mock 功能,建议你搭配这个库来提升模拟数据的真实性:
首先在终端执行命令安装mockjs
yarn add mockjs
然后再在mock/citys.js目录下编写如下的代码,实现使用mockjs模拟mock数据
import mockjs from 'mockjs';
export default {
'GET /api/tags': mockjs.mock({
'list|100': [{ name: '@city', 'value|1-100': 50, 'type|0-2': 1 }],
})
}
五、div状态机
1、dva状态机的工作流程
Umi 内置了 Dva 提供了一套状态管理方案:
2、Model配置
-
在src目录下新建models文件夹,然后以购物车为例,在
src/models
目录中创建一个shopcartModel.ts
文件src |--- models | |--- shopcartModel.ts
-
配置dva
首先要安装 @umijs/plugins
yarn add -D @umijs/plugins
然后需要在.umirc.ts配置文件中配置plugins和dva节点
// .umirc.ts
export default {
npmClient: 'yarn',
plugins: [
'@umijs/plugins/dist/model',
'@umijs/plugins/dist/dva',
],
dva:{}
}
3、具体实现步骤
-
model的基本结构
每一个仓库模块对象中,都有如下基本配置
const shopcartModel = {
// 状态机模块名称(如果没有设置该属性,默认当前文件名为模块名)
namespace: 'shopcart',
// 数据
state: {},
// 修改数据的方法
reducers: {},
// 异步方法
effects: {}
}
-
设置数据初始化
const shopcartModel = {
// 状态机模块名称(如果没有设置该属性,默认当前文件名为模块名)
namespace: 'shopcart',
// 数据
state: {
//定义初始数据
rows:[]
},
// 修改数据的方法
reducers: {},
// 异步方法
effects: {}
}
-
设置修改数据的方法
const shopcartModel = {
// 状态机模块名称(如果没有设置该属性,默认当前文件名为模块名)
namespace: 'shopcart',
// 数据
state: {
//定义初始数据
rows:[]
},
// 修改数据的方法(同步的)
reducers: {
setShopcartList(state,{payload}){
return{
...state,
rows:payload
}
}
},
// 异步方法
effects: {}
}
-
设置异步方法
import $http from '../api/http'
const shopcartModel = {
// 状态机模块名称(如果没有设置该属性,默认当前文件名为模块名)
namespace: 'shopcart',
// 数据
state: {
//定义初始数据
rows:[]
},
// 修改数据的方法(同步的)
reducers: {
setShopcartList(state,{payload}){
state.rows=payload.rows
}
},
// 异步方法
effects: {
*getShopcartListAsync({payload},{call,put}){
const result=yield call($http.shopcartList.getShopcartList)
yield put({type:'setShopcartList',payload:result.data.data})
}
}
}
export default shopcartModel
3、组件中使用操作仓库
使用之前需要安装react-redux包
yarn add react-redux
import {useEffect, useState} from 'react'
import {useSelector,useDispatch} from 'react-redux'
export default function student() {
const dispatch=useDispatch()
useEffect(()=>{
dispatch({
type:'shopcart/getShopcartListAsync'
})
},[])
const list=useSelector((state)=>{
return state.shopcart.rows
})
return (
<div>
<table>
<thead>
<tr>
<td>编号</td>
<td>名称</td>
<td>价格</td>
<td>数量</td>
<td>小计</td>
</tr>
</thead>
<tbody>
{
list.map(item=><tr key={item._id}>
<td>{item._id}</td>
<td>{item.name}</td>
<td>{item.price}</td>
<td>
<button>-</button>
{item.num}
<button>+</button>
</td>
<td>{item.price*item.num}</td>
</tr>)
}
</tbody>
</table>
</div>
)
}