0


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

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

标签: 前端 react umi

本文转载自: https://blog.csdn.net/qq_53123067/article/details/140533380
版权归原作者 亦世凡华、 所有, 如有侵权,请联系我们删除。

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

还没有评论