发布订阅模式

业务场景:有 A 和 B 两个表格,互为兄弟组件,当 A 中某个事件调用完成之后需要触发 B 表格中的更新。

解决办法:使用发布-订阅模式,在 A 表格中订阅事件,等 B 表格中发布后通知它

代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
export class SubscriptionPublish {
private eventMap: Record<string, ((params: any) => any)[]>;

constructor() {
this.eventMap = {};
}

/**
* 订阅函数
* @param key 订阅事件Key值
* @param handler 订阅事件
*/
on(key: string, handler: (params: any) => any) {
if (!this.eventMap[key]) {
this.eventMap[key] = [];
}
this.eventMap[key].push(handler);
}

/**
* 发布函数
* @param key 订阅事件Key值
* @param params 要发步到订阅事件中的参数
*/
emit(key: string, params?: any) {
if (this.eventMap[key]) {
this.eventMap[key].forEach((handler) => {
handler(params);
});
}
}

/**
* 销毁函数
* @param key
* @param handler
*/
remove(key: string, handler: (params: any) => any) {
if (this.eventMap[key]) {
// 如果该队列存在,先找到要删除函数的位置,然后剔除
const res = this.eventMap[key].indexOf(handler);
res !== -1 && this.eventMap[key].splice(res, 1);
}
}
}

// 创建一个全局实例
const defaultEvent = new SubscriptionPublish();

export default defaultEvent;

代码使用:我们在写完发布-订阅类的代码之后在里面创建一个实例,用作全局使用

  • A 表格
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const ApiTable: FC = () => {
// ...省略其他业务代码

// 刷新列表方法
const loadApi = () => {
table.reload();
};

useEffect(() => {
// 订阅刷新方法
defaultEvent.on("loadApi", loadApi);
return () => {
// 组件销毁时,销毁订阅函数
defaultEvent.remove("loadApi", loadApi);
};
}, []);

return <>// ...业务代码</>;
};

export default ApiTable;
  • B 表格
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const Index: FC = () => {

// ...省略其他业务代码

// 这里是要触发发布的地方
const handleSubmit = useCallback(async () => {

// ...省略其他逻辑处理

// 发布后,订阅函数调用
defaultEvent.emit('loadApi');
}, []);

return (
// ...业务代码
);
};

export default Index;

发布-订阅模式专门用于解决兄弟组件间通信的问题,不局限于 React ,其他的地方原理是一样的,同样可以解决类似问题的还有观察者模式。我这里只是针对于我这样的业务场景下写的。

提到发布-订阅模式就必然绕不开观察者模式,两者非常相似,很大的一个区别是发布-订阅模式多了一个第三方,即事件中心。在这个过程中,我们只需要维护一个事件中心,发布者不关心谁订阅了我的事件,订阅者不关心发布者是谁,通过事件中心来通知订阅者。

观察者模式 VS 发布订阅模式