持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第2天,点击查看活动详情
Umi,中文发音为「乌米」,是蚂蚁集团的开发的一款可扩展的企业级前端应用框架。Umi 持配置式路由和约定式路由,支持定制化请求,集成了dva框架进行状态管咯同时具有生命周期完善的插件体系,支持各种功能扩展以及业务需求,本文基于Umi文档,对常用配置以及运行时配置进行了详细讲解,脱离晦涩难懂的文档,快速上手Umi开发。
1.环境配置
首先创建一个umi3-template文件夹(名字自取),然后安装umi.js,也可以跟随官网走搭建流程Umi快速上手
npm create @umijs/umi-app
2.目录结构及基本配置
初始化的框架较为简单,当然后续我们可以根据业务需求添加config、components等文件,该框架中mock文件夹下提供了mock数据功能,src下跟我们其他项目一样,放置页面文件,.prettierrc配置Prettier代码格式化,最重要的是.umirc.ts,它是Umi框架配置的入口。它提供了三项自带的配置,nodeModulesTransform配置我们是否需要跳过node_modules目录下依赖文件的部分编译过程,可以设置为all,fastRefresh快速刷新,可以保持我们组件的状态,啥意思呢,当我们在调试某一条数据的时候,我们对数据做了操作,当修改其他内容时,我们期望框架热更新后,该条数据还能够保存操作后的状态,可以开启方便我们调试。
import { defineConfig } from 'umi';
export default defineConfig({
nodeModulesTransform: {
type: 'none',
},
routes: [
{ path: '/', component: '@/pages/index' },
],
fastRefresh: {},
});
除此之外,我们还可以设置其他配置,如项目端口号devServer,项目名称title,项目favicon:favicon,另外dynamicImport可以开启页面的按需加载,如果不开启该命令,当我们执行打包命令后会发现,所有的文件都打包到一个js文件夹中,这大大增加了我们首屏渲染的压力,开启该属性还可以增添自定义的页面加载时loading页面。
import { defineConfig } from 'umi';
import {routes} from './routes'
import {theme} from './theme'
export default defineConfig({
nodeModulesTransform: {
type: 'none',
},
routes: routes,
fastRefresh: {},
devServer:{
port:8081
},
title:'UMI3', // 标题配置
favicon:'/favicon.ico', // icon配置
dynamicImport:{
loading:"@/components/loading"
},
theme:theme
});
3.拆分配置文件
当我们真正开发项目时,项目配置内容较多,这时候就不能全部挤在.umirc.ts这个文件夹下了 ,这时候我们需要在最外层新建config文件夹,将路由、主题样式等模块拆分出去,注意删掉.umirc.ts文件,防止配置不生效。
import { defineConfig } from 'umi';
import {routes} from './routes'
import {theme} from './theme'
export default defineConfig({
nodeModulesTransform: {
type: 'none',
},
routes: routes,
fastRefresh: {},
devServer:{
port:8081
},
title:'UMI3', // 标题配置
favicon:'/favicon.ico', // icon配置
dynamicImport:{
loading:"@/components/loading"
},
theme:theme
});
4.全局样式设置及antd全局主题设置
(1)第一种方法,可以在config配置文件中使用theme配置,比如将我们的Button按钮设置为黑色。ant定制主题
export const theme= {
'@primary-color':"#333"
}
(2)第二种方法,按照框架约定,可以在src下新建global.less文件,可以进行全局样式的覆盖,注意如果需要进行antd的样式修改,需要双:root
:root:root{
.ant-btn-primary{
color: red;
}
}
5.模板约定
Umi是为我们提供了固定的html模板的,如果不满意,可以按照约定进行替换,在pages页面下新建
document.ejs
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no"
/>
<link rel="shortcut icon" type="image/x-icon" href="[/favicon.ico](http://localhost:8081/favicon.ico)" />
<link rel="stylesheet" href="[/umi.css](http://localhost:8081/umi.css)" />
<script>
window.routerBase = "/";
</script>
<script src="[/@@/devScripts.js](http://localhost:8081/@@/devScripts.js)"></script>
<script>
</script>
</head>
<body>
<div id="root"></div>
<script src="[/umi.js](http://localhost:8081/umi.js)"></script>
</body>
</html>
该模板为Umi.js默认模板我们可以复制过来,自己修改增添删除meta等内容,注意如果更改挂载id,如当前默认为root改为app,需要在config中设置如下属性
mountElementId:'app'
6.路由配置及路由授权
Umi的路由嵌套与React没有什么区别,但是Umi提供了一个路由权限控制,wrappers其后将授权组件以数组形式传入,如以下路由权限配置,如果我们想访问goods那么需要在'@/wrappers/auth'进行授权。src下新建wrappers文件夹及auth文件,当判断条件为true时返回页面组件,否则重定向到登录。
import {Redirect} from 'umi'
export default (props: any)=>{
if(true){
return <div>{props.children}</div>
}else{
return <Redirect to='/login'/>
}
}
export const routes=[
{
path: '/',
component: '@/layouts/base-layouts/index',
routes:[
{ path: '/login', component: '@/pages/login/index' },
{ path: '/register', component: '@/pages/register/index' },
{
path: '/goods',
wrappers:['@/wrappers/auth'],
component: '@/layouts/aside-layouts/index',
routes:[
{
path: '/goods',
component: '@/pages/goods/index',
},
{
path: '/goods/:id',
component: '@/pages/goods/goods-detail/index',
},
{
path: '/goods/:id/comment',
component: '@/pages/goods/goods-comment/index',
}
]
},
{ path: '/', redirect: '/login' },
]
},
{ component: '@/pages/404' },
]
7.路由跳转
分为编程式跳转及声明式跳转
(1)编程式跳转
- import {history} from 'umi
- const 组件({history})=>{}
// import { history } from 'umi';
export default function GoodsDetail({history}) {
const getComment=()=>{
history.push({
pathname:"/goods/3/comment"
})
}
return (
<div>
<button onClick={getComment}>编程式跳转</button>
</div>
);
}
- import {useHistroy} from 'umi'
import { useHistory } from 'umi';
export default function GoodsDetail() {
const history=useHistory()
const getComment=()=>{
history.push({
pathname:"/goods/3/comment",
query:{a:4}
})
}
return (
<div>
<button onClick={getComment}>编程式跳转</button>
</div>
);
}
(2)声明式
import { NavLink } from 'umi';
<NavLink to='/goods' activeStyle={{color:'#333'}}>商品</NavLink>
import { Link } from 'umi';与NavLink相比不能添加激活状态
<Link to='/goods' >商品</Link>
8.反向代理
Umi3的反向代理与webpack非常相似,在config中使用proxy属性进行配置,当然我们可以像配置路由一样将其拆分出去。
import { defineConfig } from 'umi';
import {proxy} from './proxy'
export default defineConfig({
proxy, // 反向代理
});
export const proxy= {
'/api':{
// 代理真实地址服务器
target:"https://xxx:8080",
// 可以从http代理到https
https:true,
// 依赖origin功能 如cookie
changeOrigin:true,
// 路径替换
pathRewrite:{'^/api':''}
}
}
9.umi-request请求
umi-request 是基于 fetch 封装的开源 http 请求库,旨在为开发者提供一个统一的 API 调用方式,同时简化使用方式,提供了请求层常用的功能; umi-request共两个参数,第一个参数为url 第二个参数为options
使用:
import {request} from 'umi'
export default function IndexPage() {
const getInfo =async ()=>{
const res=request('/api/data',{
method:"post",
data:{
username:'admin',
password:'123456'
}
});
}
return (
<div>
</div>
);
}
10.useRequest hooks请求
该hooks可以返回数据、状态、错误等,但是要求接口必须返回data
使用:
import {useRequest} from 'umi'
export default function IndexPage() {
// get请求
const {data,error,loading}=useRequest('/api/data')
// post请求
const {data,error,loading}=useRequest({
url:"/api/data",
method:"post",
data:{
username:'admin',
password:'123456'
}
})
}
11.dva 状态管理
Umi3中集成了dva做全局状态管理,其用法我认为官方文档写的非常清楚,大家可以自行查阅文档。 官方用法
12.运行时配置
在文章前半部分大部分是构建时配置,在config中配置,但是运行时,我们希望能够根据权限获取路由信息、以及渲染页面等操作,那么就需要动态配置。主要有以下内容
- (1)渲染前的权限校验
- (2)动态路由的读取、添加
- (3)路由监听、埋点
- (4)拦截器
stateDiagram-v2
[*] --> 项目
项目 --> src
src --> app
app --> render函数(权限校验)
app --> pathRoute函数(动态添加数据)
app --> onRouteChange函数(在路由初始加载或者切换时进行操作)
app --> request函数(发送数据请求前和发送数据请求后,做配置业务)
(1)渲染前的权限校验
在src下新建app.js,我们抛出一个Umi内置的render函数,在render函数中进行权限校验,如示例,判断是否登录,未登录则跳转登录页的判断,该函数接收一个参数oldRender,oldRender 至少调用一次进行页面渲染覆盖 如果不调用,页面将不会渲染。
import { request,history } from "umi"
export const render=async (oldRender)=>{
// 权限校验业务
const res=await request('/auth/login')
if(!res.isLogin){
history.push('/login')
}
//oldRender 至少调用一次进行页面渲染覆盖 如果不调用,页面将不会渲染
oldRender()
}
(2)动态路由的读取、添加
在src下app.js,我们再抛出一个Umi内置的pathRoutes函数,他可以接收到一个routes参数,即原来的静态路由数据。如果动态添加,则使用routes.push方式进行,实际开发中 将会对请求的路由进行filter,然后添加到原来的静态路由中。
export function pathRoutes({routes}){
// exact需要添加 否则会出现子路由不跳转情况
// require('@/pages/404')拿到模块 .default 拿到模块内容
routes.push({exact:true,component:require('@/pages/404').default})
}
(3)路由监听、埋点
在src下app.js,我们再抛出一个Umi内置的onRouteChange函数,他可以接收到4个参数,在该函数中,我们可以监听路由变化、获取路由跳转信息等,进行动态设置,数据埋点收集等操作。
export function onRouteChange({matchedRoutes,location,routes,action}){
// routes 路由集合
// matchedRoutes 当前匹配的路由及其子路由
// location及其参数
// action 路由跳转执行的操作 Push Pop 等
// 实例 根据路由修改标题
document.title=matchedRoutes[matchedRoutes.length-1].route.title || 'header'
}
(4)拦截器
在Umi中,我们可能会使用Umi自带的请求,那么这样我们就无法像以前自己封装axios请求一样进行请求拦截,设置token等操作,Umi中在app.js中配置 request 项,来为你的项目进行统一的个性化的请求设定,使用requestInterceptors请求拦击,responseInterceptors返回数据拦截以及errorConfig进行错误处理。
export const request={
// timeout:1000 延时
// errorConfig:{} 错误处理
// middlewares:[] 使用中间件
timeout: 1000,
// other axios options you want
errorConfig: {
errorHandler(){
},
errorThrower(){
}
},
requestInterceptors: [
(url,option)=>{
option.headers={token:'12121'}
// 请求地址,配置项
return {url,options}
}
],
responseInterceptors: [
(response,option)=>{
return response
}
]
}