设计模式之发布订阅
发布订阅又称观察者模式,即定义一种一对多的方式,当发布者更新一种数值,多个订阅者收到相同的数值更替。发布订阅广泛用于异步编程中。
1. 中间件(单例模式)
要使用发布-订阅 模式,我们需要使用一个对象来作为发布-订阅的中间件,并用单例模式 进行管控,避免之后的开发进行了覆盖
/**
* 以下代码用 typescript
*/
class Bus {
static bus:any;
pub:Object; // 存储所有的事件
constructor(){
if(!Bus.bus){
Bus.bus = this
}
return Bus.bus
}
static getInstance(){
if(!this.bus){
this.bus = new Bus();
}
return this.bus;
}
/**
* 订阅事件,与发布事件通过 key 作为信道
* @param key
* @param fun
*/
public addListen(key:string,fun:Function){
if(!this.pub.hasOwnProperty(key)){
this.pub[key] = []
}
this.pub[key].push(fun)
}
/**
* 移除事件
* @param key
* @param fun
*/
public removeListen(key:string,fun:Function){
if(!this.pub.hasOwnProperty(key)){
throw new Error('未注册事件')
}
this.pub = this.pub[key].filter((fn:Function)=>{
return fn !== fun
})
}
/**
* 发布事件
* @param key
*/
public emit(key:string,...args:any[]){
if(!this.pub.hasOwnProperty(key)){
throw new Error('未注册事件')
}
let fns = this.pub[key];
fns.forEach((fn:Function)=> {
fn.apply(null,args)
});
}
}
let bus1 = new Bus();
let bus2 = new Bus();
console.log(bus1 === bus2); // true
2. 发布者
/**
* 发布者
*/
const pub = {
a : 1,
add :function(){ bus1.emit('numchange', this.a+1 )}
}
我们构建一个 pub
对象,给它添加一个 add
方法,当调用它的时候,将它自己的 a
值加 1
进行发布,信道是 numchange
3. 订阅者
/**
* 订阅者1
*/
const sub1 = bus1.addListen('numchange',(num:Number)=>{
console.log(`sub1 收到的 a 是 ${num}`)
})
/**
* 订阅者2
*/
const sub2 = bus2.addListen('numchange',(num:Number)=>{
console.log(`sub2 收到的 a 是 ${num}`)
})
我们构建两个订阅者,都订阅 numchange
这个信道
4. 发布
pub.add();
//true
//sub1 收到的 a 是 2
//sub2 收到的 a 是 2
5. 完整代码(经过转义)
/**
* 以下代码用 typescript 撰写
*/
var Bus = /** @class */ (function () {
function Bus() {
this.pub = {}
if (!Bus.bus) {
Bus.bus = this;
}
return Bus.bus;
}
Bus.getInstance = function () {
if (!this.bus) {
this.bus = new Bus();
}
return this.bus;
};
/**
* 订阅事件,与发布事件通过 key 作为信道
* @param key
* @param fun
*/
Bus.prototype.addListen = function (key, fun) {
if (!this.pub.hasOwnProperty(key)) {
this.pub[key] = [];
}
this.pub[key].push(fun);
};
/**
* 移除事件
* @param key
* @param fun
*/
Bus.prototype.removeListen = function (key, fun) {
if (!this.pub.hasOwnProperty(key)) {
throw new Error('未注册事件');
}
this.pub = this.pub[key].filter(function (fn) {
return fn !== fun;
});
};
/**
* 发布事件
* @param key
*/
Bus.prototype.emit = function (key) {
var args = [];
for (var _i = 1; _i < arguments.length; _i++) {
args[_i - 1] = arguments[_i];
}
if (!this.pub.hasOwnProperty(key)) {
throw new Error('未注册事件');
}
var fns = this.pub[key];
fns.forEach(function (fn) {
fn.apply(null, args);
});
};
return Bus;
}());
var bus1 = new Bus();
var bus2 = new Bus();
console.log(bus1 === bus2);
/**
* 发布者
*/
var pub = {
a: 1,
add: function () { bus1.emit('numchange', this.a + 1); }
};
/**
* 订阅者1
*/
var sub1 = bus1.addListen('numchange', function (num) {
console.log("sub1 \u6536\u5230\u7684 a \u662F " + num);
});
/**
* 订阅者2
*/
var sub2 = bus2.addListen('numchange', function (num) {
console.log("sub2 \u6536\u5230\u7684 a \u662F " + num);
});
pub.add();
发布订阅在 react
框架中使用
发布订阅在 React 中常用于组件间的通信,其余方法还有 props,useContext ,redux
中间件
class Bus {
author: string;
// 监听方法
listen: any;
constructor() {
this.author = 'siroi';
this.listen = {};
}
// 订阅者订阅消息
addListen = (key:string, fn:any)=>{
if (!this.listen[key]) {
this.listen[key] = [];
}
this.listen[key].push(fn);
}
//移除订阅
removeListen = (key:string,fn:any)=>{
const fns = this.listen[key];
if (!fns || fns.length === 0) return;
if (!fn) {
this.listen[key] = [];
} else {
for (let l = fns.length - 1; l >= 0; l--) {
if (fn === fns[l]) {
fns.splice(l, 1);
}
}
}
}
//发布订阅
emit(key:string,...args:any[]) {
const fns = this.listen[key];
if (!fns || fns.length === 0) {
return false;
}
fns.forEach((fn:any) => {
fn.apply(this, args);
});
}
}
export default new Bus();
发布者
import { useEffect, useState } from 'react';
import Bus from './Obj';
const FaBu = () => {
const [num, setNum] = useState(0);
useEffect(() => {
console.log(num)
Bus.emit('clickFaBu', num);
}, [num]);
const clickDiv = () => {
setNum(num + 1);
};
return (
<>
<div>子组件1</div>
<div onClick={clickDiv}>点我加一</div>
</>
);
};
export default FaBu;
订阅者
import { useEffect, useState } from "react";
import bus from './Obj';
import init from "./pub";
const DingYue = ()=>{
const [num,setNum] = useState(0);
useEffect(()=>{
init();
bus.addListen('clickFaBu',(num:number)=>{
setNum(num)
})
},[])
return (
<><div>
订阅者订阅消息{num}
</div>
</>
)
}
export default DingYue
Comments | NOTHING