本文已参与「新人创作礼」活动,一起开启掘金创作之路。
助你快速了解 Ant Design Pro V5 详细配置,用于自己的项目,是真的香!
上篇文章讲了下有关Ant Design Por V5 的基础配置,接下来本菜鸟将详细讲讲实实在在的干货,主要从:国际化、数据的装载与搬运(useModel)、路由(菜单)、网络请求、数据模拟(mock) 五个方面详细解剖,
希望这篇文章能为小伙伴们一点微薄的帮助,有任何问题,或说的不对的地方,欢迎评论区讨论👏🏻👏🏻👏🏻
附上本菜鸟的 gitHub地址:Ant Design Pro V5 有喜欢的点个 Star 支持下~
去除国际化
国际化是Ant Design Pro 一个非常强大的功能,但对国内的项目并不需要要国际化,所以当自己的项目不需要这个功能时,我们可以考虑去除这个功能
我们只需要执行 npm run i18n-remove 这个命令即可,但此时我们再将 local 删掉,还是会发现有大量的报错原因是代码中只用了 umi的 useIntl 这个方法, 那么现在只需要把文件的所有代码删除就可以了
小问题
当我们去除国际化后我们还是会遇见一些小问题
1.浏览器自带的翻译功能
这是因为在 src/page/document.ejs 文件中的 lang 是 en 的原因
我们需要将它改为zh-CN就行了
2.Ant Design 的部分组件会显示英文(如日期组件)
这时我们还需要在 config/config.js 中的 locale 配置 default: 'zh-CN' 即可
数据的装载与搬运---useModel
上篇文章本菜鸟说过 V5 的useModel 相当于傻瓜式操作,不得不说蚂蚁就是牛!
首先 V5 自带一个全局状态 (initialState),用官方的话说是:
initialState 在 v5 中替代了原来的自带 model,global,login,setting 都并入了 initialState 中。我们需要删除 src/models/global.ts,src/models/login.ts,src/models/setting.ts ,并且将请求用户信息和登陆拦截放到 src/app.tsx 中
理解下官方的话:
- 首先,他是一个由官方内置的model,可以当做最外层的model
- 其次,我们可以将全局的状态放进去,以供单独的文件去调用,如:用户信息,权限等级等~
我们先来看看如何使用吧
import { useModel } from 'umi';
export default () => {
const { initialState, loading, error, refresh, setInitialState } = useModel('@@initialState');
return <>{initialState}</>
};
使用起来非常的方便,先介绍下所有参数的用途
initialState : 返回全局状态,也就是 getInitialState 的返回值
setInitialState: (state:any) => 手动设置 initialState 的值,手动设置完毕会将 loading 置为 false.
loading: getInitialState 是否处于 loading 状态,在首次获取到初始状态前,页面其他部分的渲染都会被阻止。loading 可用于判断 refresh 是否在进行中。
error: 当运行时配置中,getInitialState throw Error 时,会将错误储存在 error 中。
refresh: () => void 重新执行 getInitialState 方法,并获取新数据。
如何独自创建 model
讲完了官方自带的 initialState,接下来我们单独讲讲如何创建属于自己的 model
这块没有太多要讲解的地方,我们直接上代码吧~,以最简单的计时器即可
首先我们需要在 src/model 创建自己模块
文件位置 src/models/test/modelTest.ts
import { useState, useCallback } from 'react';
interface Props {
count?: number
}
const initInfoValue: Props = {
count: 1,
}
export default function modelTest() {
const [init, setInitValue] = useState(initInfoValue);
const [loading, setLoading] = useState(false);
const waitTime = (time: number = 2000) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(true);
}, time);
});
};
const setInit = useCallback(async(res:any) => {
setLoading(true)
await waitTime()
setLoading(false)
setInitValue({count: res})
}, [init])
const setAdd= useCallback((res:any) => {
setInitValue({ count: res +1})
}, [init])
return {
loading,
init,
setAdd,
setInit
};
}
然后在所需的页面直接通过useModel,获取就 OK 了
import React from 'react';
import { useModel } from 'umi';
import { Button } from 'antd';
export const MockModel: React.FC<any> = () => {
const { init, setInit, setAdd, loading } = useModel('test.modelTest');
return <div>
<div style={{ marginBottom: 14 }}> count 对应的值{init.count}</div>
<Button loading={loading} style={{ marginBottom: 18 }} type='primary' onClick={() => setInit(5)} >设置count为5</Button>
<br />
<Button type='primary' onClick={() => setAdd(init.count)} >累加1</Button>
</div>
}
总的来说,只要把对应的方法,值全部返回,然后在调用就OK了,是不是很简单~
性能优化
当我们存在 model 的数据越来越多,就需要使用 useModel 的第二个可选参数来进行性能优化,只消费model 中的部分参数,而不使用其他参数,并返回的值则是 useModel 最终的返回值
我们以上述为例
import React from 'react';
import { useModel } from 'umi';
import { Button } from 'antd';
export const MockModelRet: React.FC<any> = () => {
const { init, setAdds } = useModel('test.modelTest', (ret) => {
return {
init: ret.init,
setAdds: ret.setAdd
}
});
return <div>
<div style={{ marginBottom: 14 }}> count 对应的值{init.count}</div>
<Button type='primary' onClick={() => setAdds(init.count)} >累加1</Button>
</div>
}
路由详解(动态菜单)
路由的配置
首先,所有的路由都在config/routes下,我们配置路由都在此文件下进行
我们先建一个一级目录
export default [
{
path: '/test',
name: '一级目录',
icon: 'smile',
component: './Welcome'
}
]
看看此时实现的效果
而多级目录只需要使用 routes 这个参数即可,其他配置一样
我们在这里在创建一个二级目录,在创建一个二级目录的子目录,我们希望由二级目录跳入这个子目录,但在菜单上不显示,这时我们只需要使用hideInMenu即可在菜单上隐藏,但我们会发现变成了这样
这时我们发现了两个问题:一个是左边的菜单栏并没有高亮,二是面包屑展示的不对,这是因为我们写的路劲不对,我们需要把这个子页面挂载二级目录的下面就能完美解决了~
export default [
{
path: '/test',
name: '一级目录',
icon: 'smile',
routes: [
{
path: '/test',
redirect: '/test/twotest',
},
{
path: '/test/twotest',
name: '二级目录',
component: './Welcome',
},
{
path: '/test/twotest/threetest',
name: '二级目录的子页面',
component: './Welcome',
hideInMenu: true
}
]
}
]
效果:
我们再来总结下常用路由的参数(其余的参数可看官网):
-
path: 地址栏的访问路径 -
name: 名称 -
icon:前面的小图标 -
component:对应的文件夹目录 -
redirect:重定向后的地址 -
authority:权限,大型项目不建议使用,直接用动态菜单即可 -
hideInMenu: 是否影藏菜单栏 -
routes:对应的子路由
动态菜单
在 V5 中,提供两种,一种是 权限, 一种是动态菜单,在这里建议使用动态菜单,所以只介绍下动态菜单的用法。
所谓动态菜单,需要接口的配合,返回什么菜单就展示什么,在本人的案例中,我通过 mock 模拟出数据,并将它放入 utils/initData 中
有感兴趣的同学可以去 GitHub 上下载看看,我这里讲下我使用中发现的问题:
- 动态路由里的地址只能根据已配置原有的路由去打包,如果没有则会出现问题,(也就是说,不管有没有动态路由,都要在原有的路径里写上对应的名字)
- 其中配置的 component 无用,redirect的也无用。
- 动态路由的 icon不显示
- 动态路由就算隐藏了对应的路径,也可以通过地址栏输入地址进入到该页面
- redirect 重定向不管用,就算设置上也是默认原有的路由的重定向定义的,这样点击头部的时候会跳转到原有的页面
- 登录,登录的时候跳转的页面如果没有重定向地址,会跳转 / ,应为动态路由里的重定向不管用,所以会跳向原有的/页面
如有写的不对请留言指出~
解决方法:
针对问题1和问题2,我们必须跟后端协商好,这个路由必须与接口返回的字段对应,并且只需要返回对应的名称、icon、路径即可
问题3,我们需要单独写个方法来适配就行了
使用: menuData: formatter(menuData.data)
const formatter = (data: any[]) => {
data.forEach((item) => {
if (item.icon) {
const { icon } = item;
const v4IconName = toHump(icon.replace(icon[0], icon[0].toUpperCase()));
const NewIcon = allIcons[icon] || allIcons[''.concat(v4IconName, 'Outlined')];
if (NewIcon) {
try {
// eslint-disable-next-line no-param-reassign
item.icon = React.createElement(NewIcon);
} catch (error) {
console.log(error);
}
}
}
if (item.routes || item.children) {
const children = formatter(item.routes || item.children); // Reduce memory usage
item.children = children;
}
});
return data;
};
const toHump = (name: string) => name.replace(/-(\w)/g, (all: string, letter: any) => letter.toUpperCase());
针对问题5和问题6,我们进行详细的描述下:
比如说我现在原有的页面配置上A页面(第一个页面),但我在其权限下不想展示A页面,不显示的时候,就会出现这个问题
解决方法
在点击头部的方法和登录的方法(不包括重定向)跳转到获取路由的第一个上,并且,将取消原有路由的重定向。在getInitialState上统一设置,如果路径是/则自动获取第一个参数的路径,就能解决了~
链接的桥梁--网络请求
网络请求可以说是前端与后端的桥,我们需要这个桥将后端绑定起来
对于一个系统来说,请求的方法与接受的参数都是统一的,所以我们需要集中配置我们的请求模块,来适配自己的系统。
在 V5 中设置请求的模块在src/app.tsx中
在V5中我们需要在 umi 中引入,并且相对于 V4 ,V5扩张了一个配置 skipErrorHandler, 这个配置的作用是:跳过默认的错误处理,用于处理特殊的接口
import { request } from 'umi';
request('/api/user', {
params: {
name: 1,
},
skipErrorHandler: true,
});
统一地址
在上篇文章介绍了分模块打包 其本质就是通过不同的命令,打出不同的包,请求的接口就需要在这里通过prefix来配置
export const request: RequestConfig = {
prefix: process.env.NODE_ENV === "production" ? host : '/api/',
};
拦截器
每个系统对应的后端请求都不相同,所以应该在请求前和请求后做一些特定的处理,以此来帮助我们快速开发,比如:在请求的时,请求头上加入 token
因此,V5提供了两种方式,一是中间件(middlewares),另一种则是拦截器
这两种方式都可以优雅地做网络请求前后的增强处理,但中间件使用起来比较复杂,所以这里只介绍拦截器
请求拦截--requestInterceptors
首先,我们来说说请求拦截需要配置什么
- 后端的不同发送网络的格式,方式都不通,比如说配置 Content-Type
- 此外,现在大多数项目都会有一个token,用来判断
这里要说明一点 token 通常需要存储到本地的,原因是每次启动项目都会用到,当然缓存能少用就少用,尽量使用数据流做处理。
因此 我们需要设置一个变量来存储 token
另外我们需要注意一点,在未登录的时候并无token,并且在退出登录后,要清空缓存
废话有点多~ 直接看代码吧~
/**请求拦截 */
export const requestInterceptors: any = (url: string, options: RequestInit) => {
if (storageSy.token) {
const token = `Bearer ` + localStorage.getItem(storageSy.token);//存储的token
options.headers = {
...options.headers,
"Authorization": token,
'Content-Type': 'application/json',
}
}
return { url, options };
}
响应拦截 responseInterceptors
跟请求拦截一样,我们先来说说响应拦截做的的做了什么吧
- 统一的错误处理,如:在网络不好的情况下,请求不到数据,这时我们可以给一个统一的提示语来告诉用户
- 统一报错,有的时候返回的状态是不正常的,这时我们就可以做处理,给出接口给的提示语,并且我在这里统一设置了一下,如果返回的不是 200(成功)将统一设置为 false, 这样就不需要用
catch来进行捕获了。 - 用户登录时限,一个系统中,我们希望用户登录这个是有时效性,这是接口就回返回特定的状态码,来告诉我们用户信息不匹配,或者登录时间到了,这时我们需要在响应拦截中给出对应的提示,并清空缓存,退出系统 关于第二点,其实有个小问题,就是他什么都不返回,但状态码为 200 ,这时在具体页面中如何判定成功呢,其实只要判定 返回的类型 不等于 布尔值就行了
// 响应拦截
export const responseInterceptors:any = async (response: Response) => {
if (!response) {
notification.error({
description: '您的网络发生异常,无法连接服务器',
message: '网络异常',
});
return;
}
const data = await response.clone().json();
if ([10001,10008].includes(data.resultCode)) {
message.error(data.message);
localStorage.clear();
return false;
}
if (data.code !== 200) {
message.error(data.message);
return false;
}
return data.data;
}
数据模拟--mock
mock 是什么呢? 他是模拟接口请求的数据,并且可以随机生成测试数据,当后端还未好时,我们可以与后端沟通变量的名称,之后再靠 mock 来模拟数据,实现开发,等后端好了,直接替换接口就行了~~
文档请参考: mock官网
mock服务
mock 是模拟的接口,所以在正式打包后,mock 数据是无法使用,那么如果在开发时候不用mock数据,该怎么处理呢?
命令 npm run start:no-mock 就行
独立的mock服务
我们知道,有的时候 mock 数据 是可以和真实的 Api 请求并存的
但在打包后 mock 是无法使用的,那么能否启动一个 mock 服务呢?然后通过 nginx 代理到这个mock服务呢?
官方给出了一种方法: umi-serve
安装命令 yarn add global umi-serve
为了方便起见
我们可以再 package.json 中的 script 中加入 "serve":"umi-serve" 即可
下次启动 umi-serve 服务,只需在控制台中输入:npm run serve ,即可。
GET请求和POST请求
这块内容比较简单,就没必要说了,直接提供两种请求方式就ok了
'GET /api/form/queryDetail': async (req: Request, res: Response) => {
const { detail } = req.query;
if (detail === 'introduce') {
res.send(
resData({
list: introduce,
anchorList: introduceAnchorList
}
))
}
res.send({
code: 400,
detail,
message: '请输入参数'
})
}
'POST /api/domesy/queryDetail': async (req: Request, res: Response) => {
const { detail } = req.query
if(detail === 'welcome') {
res.send(
resData({
list: welcome,
anchorList: welcomeAnchorList
}
))
return
}
res.send({
code: 400,
detail,
message: '请输入参数'
})
},
到此,就结束了,希望本篇文章对你有微末的帮助~~~
关于 Ant Design Pro V5 的基本配置请看 打造属于你的Ant Design Pro V5(一)
关于 Ant Design Pro V5 工作流程请看 打造属于你的Ant Design Pro V5(三)