真的老喜欢用umi了,最近独立从0负责了好几个项目,技术栈都是umi + ant design,正好写一个小总结聊一聊umi项目的常见配置吧~
本篇文档由官方文档总结而来,如果参照以下步骤遇到问题,以官方文档为准,查看是否有版本更新,本篇文档的使用版本为umi4 + React18 + Ant design 5.x
创建umi项目
umi官方推荐使用pnpm,笔者在这里也使用了pnpm
bash
npm install -g umi pnpm # 首先全局安装umi和pnpm
mkdir umi-test
cd umi-test
pnpm dlx create-umi@latest # 初始化umi仓库(官方推荐使用pnpm,镜像源可以选择taobao,国内访问速度较快)
启用eslint、prettier、husky
如果我们初始化项目使用的是umi max,则无需安装,因为max已经包含了eslint;如果使用的是umi,则有如下两种方法安装
第一种方法:官网上的方法
比较特别的一点是:保存时会按照字母顺序对导入的包进行排序
1. 安装eslint
安装
ruby
pnpm add @umijs/lint -D # 需要先安装@umijs/lint
pnpm add -D eslint "stylelint@^14" # 目前 @umijs/lint 使用的 stylelint 版本是 v14
配置
php
// .eslintrc.js
module.exports = {
// Umi 项目
extends: require.resolve('umi/eslint'),
// Umi Max 项目
extends: require.resolve('@umijs/max/eslint'),
}
// .stylelintrc.js
module.exports = {
// Umi 项目
extends: require.resolve('umi/stylelint'),
// Umi Max 项目
extends: require.resolve('@umijs/max/stylelint'),
}
umi lint说明:
- --quiet: 可选,禁用
warn
规则的报告,仅输出error
- --fix: 可选,自动修复 lint 错误
关于版本问题,可以先查看下官网有没有更新:umijs.org/docs/guides...
2. 安装prettier
直接执行 pnpm umi g
即可自动化安装及配置
3. 安装lint-staged与husky
介绍:
lint-staged用来驱动 umi lint
命令,每次仅检查变更的文件;husky 用来绑定Git Hooks,比如pre-commit、commit-msg(对提交内容的格式规范进行检查)
-
第一种方式:基于umi的微生成器直接安装(自动安装umi内置的版本,不会出现版本错误)
umi g precommit
-
第二种方式:手动增加
安装
csharp
pnpm add lint-staged husky -D
pnpm exec husky init # 初始化husky
初始化完成后,需要手动修改 .husky/pre-commit
文件的内容:
bash
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
- npm test
+ npx lint-staged
在package.json中增加如下配置:(切记,调用pre-commit时应该先eslint后prettier)
json
{
"lint-staged": {
"*.{js,jsx,ts,tsx,css,less}": [
"umi lint"
],
"**/*": "prettier --write --ignore-unknown"
}
}
第二种方法:@umijs/fabric
这种方法的便捷性在于 @umijs/fabric
集eslint、prettier、stylelint为一体,不需要再单独配置
安装
css
pnpm i @umijs/fabric
配置
java
// .prettierrc.js
const fabric = require('@umijs/fabric');
module.exports = {
...fabric.prettier,
};
// .stylelintrc.js
const fabric = require('@umijs/fabric');
module.exports = {
...fabric.stylelint,
};
// .eslintrc.js
module.exports = {
extends: [require.resolve('@umijs/fabric/dist/eslint')],
plugins: ['react'],
// 自定义全局变量
globals: {
REACT_APP_ENV: true,
REACT_APP_API_URL: true,
},
// 根据个人习惯自定义rule
rules: {
'no-console': 'off',
'react-hooks/exhaustive-deps': 'off', // react-hooks 依赖检查
'no-empty': 'off', // catch{} 允许为空
'@typescript-eslint/no-shadow': ['off'], // 当前作用域变量名不能与父级作用域变量同名
},
};
后续安装 lint-staged
与 husky
的方法与第一种相同,如果配置此项,需要手动安装一下eslint(因为在pre-commit的时候需要执行eslint对应的命令~)
启用plugin
umi内置了很多方便的插件,如果需要使用,首先需要安装 @umijs/plugins
,并且需要在.umirc.ts中进行相关配置(如果没有正确配置,会报错!!)
css
pnpm i -D @umijs/plugins
一般来说,我会使用initialState、useModel、request、antd这些插件,那么我们需要在.umirc.ts中配置
css
// .umirc.ts
export default {
plugins: [
'@umijs/plugins/dist/initial-state', // 定义项目的初始状态
'@umijs/plugins/dist/model',
'@umijs/plugins/dist/request',
'@umijs/plugins/dist/antd',
],
antd: {},
initialState: {},
model: {},
request: {
dataField: '',
},
};
getInitialState
大多数的前端项目现在都已经开始设置 initState
,前台项目可以存储用户的登录信息,后台管理系统可以存储用户的权限信息,从而进行权限管理,不仅仅局限于 umi 中,还包括 ice 等基于 React 封装的前端框架
我们除在上述.umirc.ts中配置的内容之外,还需要在app.ts中加入如下函数,当页面初次加载时,会先调用 getInitialState
函数,等该函数获取到结果之后,才会进行其它组件的渲染(所以后台管理系统经常用它做权限管理喽)
javascript
// app.ts
export const getInitialState = async () => {
const initialData = await requestGetUserProfile();
return initialData?.data;
};
下面是调用的例子
javascript
// index.tsx
import { useModel } from 'umi';
const Home = () => {
// refresh用于重新执行 getInitialState 函数,而 setInitialState 是手动设置 initialState 值
const { initialState, loading, refresh, error, setInitialState } = useModel('@@initialState');
return <>Home</>
};
export default Home;
useModel
useModel是umi项目里超棒的一个语法糖,极大的简化了状态管理的操作
先在src下创建models文件夹,根据需求创建指定名称的ts文件,这里的示例是user.ts
typescript
// user.ts
import { useState } from 'react';
interface IPersonalInfo {
username?: string;
email?: string;
}
const UserState = () => {
const [personalInfo, setPersonalInfo] = useState<IPersonalInfo>({});
return { personalInfo, setPersonalInfo };
};
export default UserState;
调用如下
javascript
// index.tsx
import { useModel } from 'umi';
const Home = () => {
const { personalInfo } = useModel('user'); // 文件名的前缀
return <></>;
};
export default Home;
request
umi 内置了request插件,并且内置了ahooks2中的useRequest,不需要额外安装axios,并且 umi 内置了 ahooks2中的useRequest,简化了请求的代码
ahooks2中useRequest的官方文档:ahooks-v2.js.org/zh-CN/hooks...
typescript
// /service/user.ts
import { request } from 'umi';
import { BASE_URL } from './config';
export const requestLogin = async (data: any) => {
return await request(`${BASE_URL}/api/Auth/Login`, {
method: 'post',
data,
});
};
antd
内置antd组件,目前为4.x版本,并且可按需导入你需要的组件库,如果需要安装其它的版本的antd,则需要手动安装了
具体配置项参照官方文档:umijs.org/docs/max/an...
关于跨域
跨域本身是因为浏览器自己的限制,需要后端来配置白名单。如果使用umi的话,我们在前端可以使用本地代理,同样可以解决跨域问题。类比nginx服务器代理,如果匹配/api路径,则转发到后端服务器。在umi项目中,我们只需要在 .umirc.ts
中添加如下代码即可实现
javascript
export default {
proxy: {
'/api': {
'target': 'http://jsonplaceholder.typicode.com/',
'changeOrigin': true,
'pathRewrite': { '^/api' : '' },
},
},
}
全局请求拦截
在app.ts中配置即可
typescript
// app.ts
// TODO: 后端接口的报错类型
enum ErrorShowType {
SILENT = 0,
WARN_MESSAGE = 1,
ERROR_MESSAGE = 2,
NOTIFICATION = 3,
REDIRECT = 9,
}
// TODO:后端接口响应数据的类型
interface ResponseStructure {
success: boolean;
data: any;
errorCode?: number;
errorMessage?: string;
showType?: ErrorShowType;
response?: any;
}
export const request: RequestConfig = {
// 超时时间
timeout: 1000 * 10,
// 请求头
headers: { 'X-Requested-With': 'XMLHttpRequest' },
errorConfig: {
errorThrower: (res: ResponseStructure) => {
const { success, data, errorCode, errorMessage, showType } = res;
if (!success) {
const error: any = new Error(errorMessage);
error.name = 'BizError';
error.info = { errorCode, errorMessage, showType, data };
throw error; // 抛出自制的错误
}
},
errorHandler: (error: any) => {
console.log('error', error);
// TODO:配置后端接口的code
if (error.response.data?.code) {
// 请求成功发出且服务器也响应了状态码,但状态代码超出了 2xx 的范围
switch (error.response.data.code) {
case 200: {
return;
}
case 401: {
if (localStorage.getItem('token')) {
localStorage.removeItem('token');
}
if (
location.pathname !== 'login' &&
location.pathname !== '/register'
) {
history.replace('/login');
message.error('请先登录');
}
return;
}
default: {
message.error(error.response.data?.data);
return;
}
}
} else if (error.request) {
// 请求已经成功发起,但没有收到响应
message.error('服务器错误,请稍后重试!');
} else {
// 发送请求时出了点问题
message.error('请求错误,请重试!');
}
},
},
requestInterceptors: [
(url: any, options: any): any => {
if (
options.method === 'post' ||
options.method === 'put' ||
options.method === 'delete' ||
options.method === 'get'
) {
const headers = !!localStorage.getItem('token') && {
Authorization: 'Bearer ' + localStorage.getItem('token'),
};
return {
url,
options: { ...options, headers },
};
}
},
],
responseInterceptors: [
(res: any) => {
return res;
},
],
};
mock
在开发中由于某些原因,后端接口有时候不是可以及时给到,我们就需要使用mock接口了,umi 提供了开箱即用的 mock
参见官方文档:umijs.org/docs/guides...
router
参见官方文档:umijs.org/docs/guides...
指定端口运行
推荐在环境变量文件 .env
中设置 PORT=3000
,这里输入你想运行的端口即可
其它环境变量的配置参见官网:umijs.org/docs/guides...
样式
umi 支持 less,scss 等css预处理器,通常我一般定义 index.module.less
,然后在主文件中按照CSS Modules的方式去调用,如: import styles from 'index.module.less'
小知识:index.less 和 index.module.less 有什么区别?
答:index.module.less 在引入时编译器会对其进行特殊处理,每个类名都会被转换为一个独特的哈希值,来保证他的局部性,例如我们在 F12 打开控制台之后就会看到显示的类是在原有的基础上后面跟了一串哈希字符串
使用微生成器
常见的当然是生成页面和组件了,可以减少我们的代码量
生成页面:可以批量生成,指定路径生成,或者执行命令后再进行选择
bash
umi g page page1 page2 a/nested/page3
umi g page a/nested/page3
umi g page
生成组件:umi g component
基于上述两个命令,umi 也可以提前设置他们的模板,这样就更方便了
更多微生成器参见官方文档:umijs.org/docs/guides...