Umi3全配置,快速上手Umi开发

2,209 阅读8分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第2天,点击查看活动详情

Umi,中文发音为「乌米」,是蚂蚁集团的开发的一款可扩展的企业级前端应用框架。Umi 持配置式路由和约定式路由,支持定制化请求,集成了dva框架进行状态管咯同时具有生命周期完善的插件体系,支持各种功能扩展以及业务需求,本文基于Umi文档,对常用配置以及运行时配置进行了详细讲解,脱离晦涩难懂的文档,快速上手Umi开发。

1.环境配置

首先创建一个umi3-template文件夹(名字自取),然后安装umi.js,也可以跟随官网走搭建流程Umi快速上手

npm create @umijs/umi-app

2.目录结构及基本配置

image.png

初始化的框架较为简单,当然后续我们可以根据业务需求添加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
});

image.png

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
        }
    ]

}