前言:
整个项目是基于Antd Pro 来实现的一个中后台管理项目,在实现一个列表搜索查询的功能时具体需求是这样的:列表中保存好搜索的参数,点击某条列表数据详情时跳转到详情页,点击浏览器返回时也要保持上一次的状态,例如:我查看第三页的某条数据详情时再返回列表也要是第三页的数据,点击切换左边菜单栏时要将上一次的列表查询的参数全部清除掉~,我一想这不挺简单的吗?直接在左边菜单栏时点击事件中清除models的数据不就行了(我用的是antd pro 自带的useModels 存储我搜索的参数的),后面我发现左边的菜单栏都是通过路由数组来配置的,然后antd pro 内部获取路由数组然后自动渲染成Menu的,没法加点击事件。。。,
后面查看官方api文档发现可以在运行时配置有个方法:patchRoutes,可以在这里面对路由中每个菜单项注入一个点击事件去做处理,点击菜单项的时候就调用useModel重置的方法去重置上次列表搜索的参数,但我发现这个根本用不了useModel的方法,因为antd pro 中app.tsx是运行时的一个配置文件,里面是用不了hooks函数的,后面就想到了写一个事件监听,在点击的时候触发一个‘reset-model’,然后再Header组件中监听这个事件然后重置useModel的数据,下面是具体步骤。。。
1、定义我们的Model数据
model我们用了ts去写,主要是用的时候方便有提示,其实useModel写法就跟自定义hooks基本一致的,我也在里面具体也是用了一个自己自定义的useReducer,还有就是三个方法:更新、重置、重置所有 ,下面是具体代码,一些初始值的ts类型定义我就不放出来了,主要是看下自定义的一个reducer。
import { useReducer } from "react";
import {
initJinJianParams,
initialBaobeiParams,
initJiaofeiParams,
initTuikuanParams,
ActionTypes,
liuchengTreeData,
initPigaiParams,
initTuibaoParams,
initBillAnjianParams,
} from "./initstate";
interface UpdateQueryParamsAction {
type: typeof ActionTypes.UpdateQueryParams;
identifier: keyof CombinedState;
payload: Partial<JinJianParamsType | BaobeiParamsType | JiaofeiParamsType | TuikuanParamsType | FukuandanParamsType | GuaZhangParamsType>;
}
interface ResetQueryParamsAction {
type: typeof ActionTypes.ResetQueryParams;
identifier: keyof CombinedState;
payload: Partial<JinJianParamsType | BaobeiParamsType | JiaofeiParamsType | TuikuanParamsType | FukuandanParamsType | GuaZhangParamsType>;
}
interface ResetAllQueryParamsAction {
type: typeof ActionTypes.ResetAllParams;
identifier: keyof CombinedState;
payload: Partial<JinJianParamsType | BaobeiParamsType | JiaofeiParamsType | TuikuanParamsType | FukuandanParamsType | GuaZhangParamsType>;
}
type CombinedActions = UpdateQueryParamsAction | ResetQueryParamsAction | ResetAllQueryParamsAction;
}
interface CombinedState {
jinJianParams: QUERY.JinJianParamsType;
baobeiParams: QUERY.BaobeiParamsType;
jiaofeiParams: QUERY.JiaofeiParamsType;
tuikuanParams: QUERY.TuikuanParamsType;
pigaiParams: QUERY.PigaiParamsType;
tuibaoParams: QUERY.TuibaoParamsType;
billAnjianParams: QUERY.BillAnjianParamsType;
}
const initialState: CombinedState = {
jinJianParams: initJinJianParams,
baobeiParams: initialBaobeiParams,
jiaofeiParams: initJiaofeiParams,
tuikuanParams: initTuikuanParams,
pigaiParams: initPigaiParams,
tuibaoParams: initTuibaoParams,
billAnjianParams: initBillAnjianParams,
};
// 自定义reducer
function combinedReducer(state: CombinedState, action: QUERY.CombinedActions): CombinedState {
switch (action.type) {
case ActionTypes.UpdateQueryParams:
return {
...state,
[action.identifier]: {
...state[action.identifier],
...action.payload,
},
};
case ActionTypes.ResetQueryParams:
return {
...state,
[action.identifier]: action.payload,
};
case ActionTypes.ResetAllParams:
return initialState;
default:
return state;
}
}
export default () => {
const [state, dispatch] = useReducer(combinedReducer, initialState);
// 更新参数
const updateQueryParams = <T extends keyof CombinedState>(identifier: T, newParams: Partial<CombinedState[T]>) => {
dispatch({
type: ActionTypes.UpdateQueryParams,
identifier,
payload: newParams,
});
};
// 重置
const resetQueryParams = <T extends keyof CombinedState>(identifier: T, newParams: Partial<CombinedState[T]>) => {
dispatch({
type: ActionTypes.ResetQueryParams,
identifier,
payload: newParams,
});
};
// 重置所有的参数
const resetAllQueryParams = () => {
dispatch({
type: ActionTypes.ResetAllParams,
identifier: "jinJianParams",
payload: {},
});
};
return {
state,
initJinJianParams,
initJiaofeiParams,
initialBaobeiParams,
initTuikuanParams,
initPigaiParams,
initTuibaoParams,
liuchengTreeData,
updateQueryParams,
resetQueryParams,
resetAllQueryParams,
initBillAnjianParams,
};
};
在list组件中我们这样引入就行了,所有的数据都存在state中了,使用的时候可以从state中取出来:state.jinJianParams;
const { initJinJianParams, state, liuchengTreeData, updateQueryParams, resetQueryParams } = useModel("list.ywlist");
2、自定义事件监听器
因为每次点击菜单栏时都会触发一个事件监听,所以我们监听的时候提供一个方法来绑定只执行一次的事件监听器,首先我们在运行时配置文件app.tsx中的patchRoutes方法中菜单栏点击触发事件:
export function patchRoutes({ routes }: { routes: IRoute[] }): void {
routes.forEach(function addTitleClick(route: IRoute) {
if (route.routes) {
route.onTitleClick = ({ key }: { key: string }): void => {
// 处理菜单项点击事件
if(key != '' && key != undefined){
eventBus.emit('reset-modal');
}
};
route.routes.forEach(addTitleClick);
}
});
}
在header组件中监听这个事件:
自定义监听代码:
// 定义一个事件监听器类型,它接受任意数量的参数
type EventListener = (...args: any[]) => void;
// 定义一个事件总线类
class EventBus {
// 定义一个私有属性,用于存储事件及其对应的监听器数组
private events: Map<string, EventListener[]>;
constructor() {
// 在构造函数中初始化事件存储为一个空的Map
this.events = new Map();
}
// 提供一个方法来绑定事件监听器
public on(event: string, listener: EventListener): void {
// 先尝试获取已经注册的监听器数组,如果没有则使用空数组
const listeners = this.events.get(event) ?? [];
// 将新的监听器添加到数组中
listeners.push(listener);
// 更新存储中的监听器数组
this.events.set(event, listeners);
}
// 提供一个方法来绑定只执行一次的事件监听器
public once(event: string, listener: EventListener): void {
// 定义一个包装器函数,它首先解除绑定,然后执行原始监听器
const onceWrapper: EventListener = (...args) => {
this.off(event, onceWrapper);
listener.apply(this, args);
};
// 使用包装器函数作为监听器,绑定到事件上
this.on(event, onceWrapper);
}
// 提供一个方法来移除某个事件的一个指定监听器
public off(event: string, listener: EventListener): void {
// 尝试获取事件对应的监听器数组
const listeners = this.events.get(event);
if (listeners) {
// 找到要移除的监听器在数组中的索引
const index = listeners.indexOf(listener);
if (index >= 0) {
// 移除该监听器
listeners.splice(index, 1);
// 如果数组为空,则从存储中移除此事件的记录
if (listeners.length === 0) {
this.events.delete(event);
} else {
// 更新存储中的监听器数组
this.events.set(event, listeners);
}
}
}
}
// 提供一个方法来触发某个事件,调用所有绑定的监听器,并传递参数
public emit(event: string, ...args: any[]): void {
// 获取事件对应的监听器数组
const listeners = this.events.get(event);
if (listeners) {
// 创建监听器数组的一个浅拷贝,以防在执行监听器时修改原数组
const toCall = listeners.slice();
// 遍历并执行每个监听器,传递参数
toCall.forEach(listener => listener.apply(this, args));
}
}
}
// 创建事件总线的一个实例
const eventBus = new EventBus();
// 导出这个实例,以便在其他文件中使用
export default eventBus;
总结:
后面应用到项目上暂时没发现什么问题,不过我觉得这个场景还是比较少见的,应该大部分都用不到这种,自己记录一下,免得下次用到又忘记了