UMI是蚂蚁金服的底层前端框架,也是一个基于React的企业级前端应用框架,它提供了开箱即用的项目脚手架和插件化的配置,如路由构建、部署测试、文档工具、请求库等,帮助开发者快速搭建和管理复杂的前端项目,其设计目标是提高前端项目的开发效率和可维护性,尤其适用于大型复杂项目的开发与管理。
初识Umi Max
了解框架:为了方便开发者更加方便的使用umi提供的插件,umi团队在这些插件开源的基础上,直接将其集成到一起,打造了@umijs/max,让开发者直接可以通过脚手架马上获得和蚂蚁集团开发umi应用一样的开发体检,只需要在使用create-umi选择Ant Design Pro模板,就能使用@umijs/max来创建项目了,可以参考官方文档:地址 ,详细了解umi max的相关开发:
因为umi max是使用Ant Design Pro模板进行开发的,所以我们也需要了解 Ant Design Pro 对应相关配置和API的使用,通过查阅官方文档进行详细了解:
当然我们在使用antd的时候,有一些组件的是十分细碎的,这里我们可以参考ProComponents ,其可以让让中后台开发更简单,如下所示:
像登录表单的内容,ProComponents组件库已经帮助我们封装好了,如下图所示:
安装项目:接下来我们开始安装umi max项目,安装的方式和umi项目命令一样,只需要在进行模板选择的时候,选择Ant Design Pro模板即可:
安装完之后直接拖到编辑器中执行 pnpm run dev 运行项目即可,最终呈现的效果如下所示:
当然在使用 Ant Design Pro 的时候,除了使用 umi 进行安装项目,我们也可以使用官方推荐给我们的pro-cli脚手架进行安装项目,如下图所示:
Umi Max数据流
官方文档给我们介绍到,umi max给我们内置了数据流管理插件,它是一种基于hooks范式的轻量级数据管理方案,可以在umi项目中管理全局的共享数据,从官方文档可以看到umi max规定的数据流和相关命名规范方面的内容,如下所示:
这里我们在src下的models目录下新建一个ts文件,用于全局状态管理的设置,这里我们可以使用react提供的相关API函数进行书写,书写的方式有点类似pinia状态管理的写法,条理十分清晰:
import { useState, useCallback,useEffect } from "react";
export default function countModel() {
const [count, setCount] = useState(0);
// setCount修改状态是异步的,所以需要使用useCallback包裹一下
const add = useCallback(() => setCount(count + 1) , [count]);
const minus = useCallback(() => setCount(count - 1), [count]);
// 设置两秒之后修改count的值
useEffect(() => {
setTimeout(() => {
setCount(100);
}, 2000)
}, []);
// 返回count和两个方法
return {
count,
add,
minus
}
}
定义好仓库之后,接下来我们就需要使用仓库中的数据了,使用方式也是非常简单,直接借助umi框架提供的api函数useModel即可,具体代码如下所示:
最终呈现的效果如下所示:
性能优化:官方文档提供了一个性能优化的方案,useModel() 方法可以接受可选的第二个参数,当组件只需要使用Model中的部分参数,而对其它参数的变化不感兴趣时,可以传入一个函数进行过滤。
组件并不关心计数器Model中的counter值,只需要使用Model提供的increment()和decrement()方法,于是传入了一个函数作为useModel() 方法的第二个参数,该函数的返回值将作为useModel()方法的返回值,这样过滤掉了counter这一频繁变化的值,避免了组件重复渲染带来的性能损失,以实现计数器的操作按钮为例:
// src/components/CounterActions/index.tsx
import { useModel } from 'umi';
export default function Page() {
const { add, minus } = useModel('counterModel', (model) => ({
add: model.increment,
minus: model.decrement,
}));
return (
<div>
<button onClick={add}>add by 1</button>
<button onClick={minus}>minus by 1</button>
</div>
);
};
全局初始状态:@umi/max内置了全局初始状态管理插件,可以快速构建并在组件内获取umi项目全局的初始状态,全局初始状态在整个umi项目的最开始创建,编写src/app.ts的导出方法getInitialState(),其返回值将成为全局初始状态。例如:
// src/app.ts
import { fetchInitialData } from '@/services/initial';
export async function getInitialState() {
const initialData = await fetchInitialData();
return initialData;
}
现在,各种插件和定义的组件都可以通过useModel('@@initialState')直接获取到这份全局的初始状态,如下所示:
import { useModel } from 'umi';
export default function Page() {
const { initialState, loading, error, refresh, setInitialState } =
useModel('@@initialState');
return <>{initialState}</>;
};
接口请求
request:umi提供了内置的请求接口的API函数,它基于axios和ahooks的useRequest提供了一套统一的网络请求和错误处理方案,如下:
import { request } from '@umijs/max';
然后我们在接口文件里面直接使用类似axios的写法即可,接口引入接口函数调用,示例如下:
export async function testApi() {
return request("https://api.uomg.com/api/rand.qinghua")
}
最终呈现的效果如下所示:
useRequest:官方文档也是给我们提供了useRequest这个API,帮我我们更好的去消费数据:
import { useRequest } from 'umi';
export default function Page() {
const { data, error, loading } = useRequest(() => {
return services.getUserList('/api/test');
});
if (loading) {
return <div>loading...</div>;
}
if (error) {
return <div>{error.message}</div>;
}
return <div>{data.name}</div>;
};
上面的是什么意思呢?就是说当我们创建好接口函数之后,想要调用接口就可以使用useRequest对数据进行相应的处理,这里给出基础案例:
上面代码中可以看到我们是直接使用了data属性就能获取对应的数据,而不需要再通过链式操作一层一层的去寻找我们的数据,这是为啥呢?原来umi已经帮我们封装好了配置项:
我们在构建时的配置项中已经配置好了相应的属性下的值,从而不需要再data.content去拿数据:
请求响应报错拦截:在 src/app.ts 中你可以通过配置 request 项,来为你的项目进行统一的个性化的请求设定。
import type { RequestConfig } from '@umijs/max';
export const request: RequestConfig = {
timeout: 1000,
errorConfig: {
errorHandler(){
},
errorThrower(){
}
},
requestInterceptors: [],
responseInterceptors: []
};
这里官方给出一个完整的运行时配置示例,以帮助能够更好的去为自己的项目设定个性化的请求方案:
import { RequestConfig } from './request';
// 错误处理方案: 错误类型
enum ErrorShowType {
SILENT = 0,
WARN_MESSAGE = 1,
ERROR_MESSAGE = 2,
NOTIFICATION = 3,
REDIRECT = 9,
}
// 与后端约定的响应数据格式
interface ResponseStructure {
success: boolean;
data: any;
errorCode?: number;
errorMessage?: string;
showType?: ErrorShowType;
}
// 运行时配置
export const request: RequestConfig = {
// 统一的请求设定
timeout: 1000,
headers: {'X-Requested-With': 'XMLHttpRequest'},
// 错误处理: umi@3 的错误处理方案。
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, opts: any) => {
if (opts?.skipErrorHandler) throw error;
// 我们的 errorThrower 抛出的错误。
if (error.name === 'BizError') {
const errorInfo: ResponseStructure | undefined = error.info;
if (errorInfo) {
const { errorMessage, errorCode } = errorInfo;
switch (errorInfo.showType) {
case ErrorShowType.SILENT:
// do nothing
break;
case ErrorShowType.WARN_MESSAGE:
message.warn(errorMessage);
break;
case ErrorShowType.ERROR_MESSAGE:
message.error(errorMessage);
break;
case ErrorShowType.NOTIFICATION:
notification.open({
description: errorMessage,
message: errorCode,
});
break;
case ErrorShowType.REDIRECT:
// TODO: redirect
break;
default:
message.error(errorMessage);
}
}
} else if (error.response) {
// Axios 的错误
// 请求成功发出且服务器也响应了状态码,但状态代码超出了 2xx 的范围
message.error(`Response status:${error.response.status}`);
} else if (error.request) {
// 请求已经成功发起,但没有收到响应
// \`error.request\` 在浏览器中是 XMLHttpRequest 的实例,
// 而在node.js中是 http.ClientRequest 的实例
message.error('None response! Please retry.');
} else {
// 发送请求时出了点问题
message.error('Request error, please retry.');
}
},
},
// 请求拦截器
requestInterceptors: [
(config) => {
// 拦截请求配置,进行个性化处理。
const url = config.url.concat('?token = 123');
return { ...config, url};
}
],
// 响应拦截器
responseInterceptors: [
(response) => {
// 拦截响应数据,进行个性化处理
const { data } = response;
if(!data.success){
message.error('请求失败!');
}
return response;
}
]
};
当然unimax还有一些其他有趣的功能,这里就不再一一赘述了,感兴趣的朋友可自行查阅文档,后面项目中遇到的话,博主在进行讲解。
版权归原作者 亦世凡华、 所有, 如有侵权,请联系我们删除。