当前位置: 首页 > news >正文

Umi+React+Xrender+Hsf项目开发总结

一、菜单路由配置

1.umirc.ts 中的路由配置

.umirc.ts 文件是 UmiJS 框架中的一个配置文件,用于配置应用的全局设置,包括但不限于路由、插件、样式等。

import { defineConfig } from 'umi';
import config from './def/config';export default defineConfig({plugins: ['@umijs/plugins/dist/model', '@umijs/plugins/dist/request'],model: {},request: {},title: '客服管理后台',publicPath: config.publicPath,favicons: [// 'https://domain.com/favicon.ico',//设置icon],esbuildMinifyIIFE: true,history: {type: 'hash',},routes: [{path: '/',component: '@/pages/business/index',},{path: '/public',component: '@/pages/publicInstance/index',layout: false,ignoreAuth: true,},{path: '/common',name: '通用',icon: 'menu',routes: [{path: '/common/business',name: '业务线管理',component: '@/pages/business/index',},{path: '/common/peopleManage',name: '人员管理配置',component: '@/pages/index',},{path: '/common/skills',name: '技能组管理配置',component: '@/pages/skills/index',},{path: '/common/dialogChange',name: '转交配置',component: '@/pages/dialogChange/index',},],},// 其他路由配置...],
});
主要配置项解释:
  1. defineConfig:

    • 这是一个从 umi 导入的函数,用来创建 Umi 配置对象。它帮助开发者更方便地编写和组织配置。
  2. plugins:

    • 定义使用的插件列表。这里使用了两个插件:@umijs/plugins/dist/model 和 @umijs/plugins/dist/request,它们分别用于状态管理和请求处理。
  3. routes:

    • 定义应用的路由表,每个路由对象可以有以下属性:
      • path: 路由路径。
      • component: 对应路径下加载的组件。
      • routes: 子路由数组,允许嵌套路由。
      • layout: 是否使用布局,默认是 true。当设置为 false 时,表示该路由下的页面不使用全局布局。
      • ignoreAuth: 忽略权限验证,对于无需登录即可访问的公共页面非常有用。
示例分析:
  • 根路径 /:

    • 当用户访问根路径时,会加载 @/pages/business/index 组件。
  • 免登录页面 /public:

    • 访问此路径时,加载 @/pages/publicInstance/index 组件,且设置了 layout: false 和 ignoreAuth: true,意味着这个页面不会使用全局布局,并且不需要进行权限验证。
  • 通用模块 /common:

    • 包含四个子路由,每个子路由都有自己的 pathname 和对应的 component。这些子路由都归属于“通用”分类下,通过 icon 属性可以在侧边栏或导航中显示相应的图标。

2.menuConfig.ts 中的菜单项配置

menuConfig.ts 文件则通常用于定义侧边栏或顶部导航栏的菜单结构。

import React from 'react';
import {BuildOutlined,
} from '@ant-design/icons';
import { MenuProps } from 'antd';// 定义菜单项类型
export type MenuItem = NonNullable<MenuProps['items']>[number];
export const menuData: MenuItem[] = [{key: 'common',icon: React.createElement(BuildOutlined),label: '通用',children: [{key: '/common/business',label: '业务线管理',},// ...更多菜单项],},// ...更多菜单组
];

1. MenuProps['items']

这是从 Ant Design 的 MenuProps 类型中取出 items 属性的类型。

1. NonNullable<...>

  • NonNullable<T> 是 TypeScript 内置的一个工具类型,用于从类型 T 中排除 null 和 undefined。换句话说,它将类型 T 中可能存在的 null 或 undefined 移除掉。

3. [number] 

  • 从 (MenuItemType)[] 中提取出单个元素的类型,即 MenuItemType

二、hsf接口的调用

1、配置服务信息

import business from '@/pages/business';
import AccessProcess from '@/pages/entrance/accessProcess';
import { access } from 'fs';
import java from 'js-to-java';
import { request } from 'umi';export const isDaily = window.location.host.match(/localhost|.alibaba.net|-test.uc.alibaba-inc.com|30.211.81.4/,
);
export const version1 = isDaily ? '1.0.0.DAILY' : '1.0.0';
export const version2 = isDaily ? '2.0.1.DAILY' : '2.0.1';
export const version3 = isDaily ? '2.0.0.KF.DAILY' : '2.0.0.KF';// 固定的hsf服务信息
const servicer: any = {process: {appName: 'cs-xadmin',pathname: 'xxxx',version: isDaily ? '2.0.1.DAILY' : '2.0.1',},oldProcess: {appName: 'cs-xxflow',pathname: 'xxxx',version: isDaily ? '2.0.1.DAILY' : '2.0.1',},
};
/*** hsf通用调取方法* @param {string} key 对应服务key值(获取pathname:version)* @param {string} className 类名* @param {string} action 方法名* @param {string} appName 应用名* @param {any} data 参数**/
export const hsfApi = async (key: string, { className, action, data }: any) => {// 如果是日常环境 & 路径上带有connectToLocalHsf参数,则调用本地hsf,把version中的DAILY 替换为 LOCALlet version = servicer[key]?.version;if (isDaily && window.location.hash.includes('connectHsf=local')) {version = servicer[key].version.replace('DAILY', 'LOCAL');}return request(`/api/hsf?action=${action}`, {method: 'POST',data: {pathname: `${servicer[key]?.pathname}:${version}`,action,appName: servicer[key].appName,data:data && className? [java(className, {...data,}),]: [],},});
};
pathname:
 appName:

 

2、封装请求函数

// @ts-ignore
/* eslint-disable */
import { hsfApi } from '@/utils/api';/*** 分页查询技能组溢出规则* @param params 查询参数*/
export async function pageList(params: {pageNo?: number;pageSize?: number;name?: string;fromSkillgroupId?: number;toSkillgroupId?: number;
}) {return hsfApi('onlineDispatch', {className: 'com.uc.cs.xadmin.client.param.dispatchschedule.PageOnlineDispatchScheduleParam',action: 'pageList',data: {...params,}});
className: 

className: 

 

 

参数解释:
  • 'onlineDispatch'
    • 表示要调用的服务名,对应你在 api.ts 中配置的 servicer 对象里的键值。
    • 会去查找该服务的应用名、路径、版本等信息。
  • className
    • 表示你这次请求的参数在后端对应的 Java 类型。
  • action: 'pageList'
    • 表示你要调用的方法名,即后端服务提供的某个接口方法。
  • data: { ...params }
    • 把传入的 params 参数展开并传入请求体中。
    • 最终会被包装成一个 Java 对象发送到后端

 

三、页面开发

1、父组件

(1)懒加载

import React from 'react';
const AddEditModal = React.lazy(() => import('./components/AddEditModal'));

 React.lazy 是 React 提供的一个用于实现代码分割的功能。通过 React.lazy 和动态 import() 语法,你可以按需加载组件,而不是在应用初始化时就加载所有组件。

(2)useMemo

  const schema = useMemo(() => getOnlineDispatchSchema(groupValues), [groupValues]);

useMemo 是一个用于记忆化的 Hook,它接收两个参数:

  1. 一个创建函数:该函数返回需要被记忆化的值。
  2. 一个依赖项数组:只有当这些依赖项发生变化时,才会重新计算记忆化的值;否则将返回之前记忆化的结果。
  const columns: any = useMemo(() => {   {title: '最后修改信息',dataIndex: 'modifyInfo', // 可以不绑定真实字段,仅占位width: 200,render: (_: any, record: any) => {const modifyTime = dayjs(record.modifyTime).format('YYYY-MM-DD HH:mm:ss');const modifier = record.modifier || '未知';return (<div style={{ lineHeight: 1.5 }}><div>{modifyTime}</div><div>{modifier}</div></div>);},},
}

(3)render

render: (_: any, record: any) => {} 的用法
  1. _:当前列的值。有时你可能不需要使用这个值,所以通常用下划线 _ 来表示忽略这个参数。

  2. record:当前行的所有数据。这是一个对象,包含了该行所有字段的信息。

(4)请求缓存避免发起多个相同请求 

// 在组件外部创建缓存
const skillGroupsCache = {data: null as SkillGroup[] | null,promise: null as Promise<SkillGroup[] | null> | null // 允许Promise返回null
};const fetchSkillGroups = async () => {if (skillGroupsCache.data) return skillGroupsCache.data;if (skillGroupsCache.promise) return skillGroupsCache.promise;skillGroupsCache.promise = (async () => {try {const result = await index({/* 参数 */});skillGroupsCache.data = result.code === 200 ? result.data.rows?.map((v: SkillGroupResponse)  => ({ label: v.name, value: v.id })) || []: [];return skillGroupsCache.data;} catch (error) {console.error('获取技能组失败:', error);return [];} finally {skillGroupsCache.promise = null;}})();return skillGroupsCache.promise;
};
缓存对象 skillGroupsCache

  • data:用于存储从服务器获取到的技能组数据。如果已经成功获取了数据,则直接使用缓存的数据,无需再次发起网络请求。

  • promise:用于存储正在进行中的异步请求,这样可以确保在同一个数据获取过程中,如果有多个地方同时请求相同的数据,它们将共享同一个请求结果,而不是各自发起新的请求。

  1. 检查缓存数据

    • 首先检查 skillGroupsCache.data 是否已经有值。如果有,说明之前已经成功获取过数据,直接返回缓存的数据,避免重复请求。
  2. 检查进行中的请求

    • 如果没有缓存的数据,但 skillGroupsCache.promise 不为空,说明当前有一个正在进行中的请求。在这种情况下,直接返回这个正在进行中的请求(Promise),所有调用者将等待同一个请求的结果,而不是各自发起新的请求。
  3. 发起新请求

    • 如果既没有缓存的数据也没有正在进行中的请求,则创建一个新的异步请求来获取数据,并将其存储在 skillGroupsCache.promise 中。
    • 在请求成功后,将获取到的数据存储在 skillGroupsCache.data 中,并清空 skillGroupsCache.promise
    • 如果请求失败,记录错误信息并返回一个空数组。
  4. 清理

    • 无论请求成功还是失败,在 finally 块中都会将 skillGroupsCache.promise 设置为 null,以便后续的请求可以正常发起。

(5)初始化状态锁

 // 添加初始化状态锁const initializedRef = useRef(false);// 初始化数据useEffect(() => {console.log('initializedRef.current',initializedRef.current);if (initializedRef.current) return;initializedRef.current = true;const initData = async () => {setSpinning(true);try {setTableHeight(useInitTableHeight(-10));const [groups, pageData] = await Promise.all([fetchSkillGroups(),fetchPageList({ pageNo: 1, pageSize: 20 }) // 合并初始化请求]);setGroupValues(groups || []);setRawTableData(pageData.data.data.list || []);// setInitialized(true);} catch (error) {console.error('初始化失败:', error);} finally {setSpinning(false);}};initData();
}, []);

通过 if (initializedRef.current) return; 来判断是否已经执行过初始化操作。如果已经初始化则直接返回,不重复执行初始化逻辑。

使用 useRef 而不是 useState 的原因
  • useRefuseRef 返回一个可变的引用对象,其 .current 属性在组件的整个生命周期内保持不变。修改 .current 属性不会触发组件的重新渲染

  • useState:每次调用 setState 函数都会导致组件重新渲染。如果我们使用 useState 来管理初始化状态,那么每当更新该状态时,都会导致组件重新渲染,这可能会引起性能问题或意外的行为。

(6)??和...运算符

  // 构造请求参数const payload: Record<string, any> = {pageNo: current ?? 1,pageSize: pageSize ?? 20,...(onlineDispatchName && { name: onlineDispatchName }),...(overflowSkillGroup && { fromSkillgroupId: overflowSkillGroup }),...(inflowSkillGroup && { toSkillgroupId: inflowSkillGroup }),...(modifier && { modifier }),};
??对比逻辑或运算符 (||)

在 ES2020 之前,开发者通常使用逻辑或运算符 (||) 来提供默认值。但是,|| 运算符会在左侧操作数是任何假值(如 0, false, '' 等)时也返回右侧的默认值

let current = 0;
console.log(current || 1); // 输出: 1 (可能不符合预期)
console.log(current ?? 1); // 输出: 0 (符合预期)

相关文章:

  • Python引领前后端创新变革,重塑数字世界架构
  • vscode预览模式(点击文件时默认覆盖当前标签,标签名称显示为斜体,可通过双击该标签取消)覆盖标签、新窗打开
  • Redis再次开源!reids8.0.0一键安装脚本分享
  • Web前端技术栈:从入门到进阶都需要学什么内容
  • string--OJ3
  • 数据智能重塑工业控制:神经网络在 MPC 中的四大落地范式与避坑指南
  • 学习笔记:黑马程序员JavaWeb开发教程(2025.3.29)
  • 第16章 Python数据类型详解:列表(List)与运维开发实践
  • Cloudera CDP 7.1.3 主机异常关机导致元数据丢失,node不能与CM通信
  • 大数据技术全景解析:Spark、Hadoop、Hive与SQL的协作与实战
  • Qt开发经验:回调函数的线程归属问题及回调函数中更新控件的问题
  • ASP.NET MVC4 技术单选及多选题目汇编
  • (九)PMSM驱动控制学习---分流电阻采样及重构
  • 2:点云处理—3D相机开发
  • 追踪大型语言模型的思想(上)(来自针对Claude的分析)
  • 鸿蒙开发——1.ArkTS声明式开发(UI范式基本语法)
  • ClimateCatcher专用CDS配置教程
  • 如何在自己的服务器上部署静态网页并通过IP地址进行访问
  • 电池管理系统BMS三级架构——BMU、BCU和BAU详解
  • 前端面试测试题目(一)
  • 王毅同印度国家安全顾问多瓦尔通电话
  • 新华时评:中国维护国际经贸秩序的立场坚定不移
  • 祝贺!苏翊鸣成功解锁“2160”
  • 领证不用户口本,还需哪些材料?补领证件如何操作?七问七答
  • “科创板八条”后百单产业并购发布,披露交易金额超247亿
  • 习近平会见古巴国家主席迪亚斯-卡内尔