Umi Max 详解:打造高性能、可扩展的前端应用

亦世凡华、 2024-08-16 10:33:05 阅读 59

UMI是蚂蚁金服的底层前端框架,也是一个基于React的企业级前端应用框架,它提供了开箱即用的项目脚手架和插件化的配置,如路由构建、部署测试、文档工具、请求库等,帮助开发者快速搭建和管理复杂的前端项目,其设计目标是提高前端项目的开发效率和可维护性,尤其适用于大型复杂项目的开发与管理。

目录

初识Umi Max

Umi Max数据流

接口请求

初识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状态管理的写法,条理十分清晰:

<code>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这一频繁变化的值,避免了组件重复渲染带来的性能损失,以实现计数器的操作按钮为例:

<code>// 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,帮我我们更好的去消费数据:

<code>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 项,来为你的项目进行统一的个性化的请求设定。

<code>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还有一些其他有趣的功能,这里就不再一一赘述了,感兴趣的朋友可自行查阅文档,后面项目中遇到的话,博主在进行讲解。



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。