一、观察者模式
概要:需要理解目标与观察者的关系
- 目标与观察者之间的关系类似老师与学生的关系。老师办了一个补习班,学生必须先到老师那听课,老师开始教学。
- 学生们听懂了老师的课,更新自己的知识。 老师和学生们有直接的联系,他们之间面对面的关系。
总结:
- 观察者模式,多用于单个应用内部,没有第三方来实现调度。 定义了对象之间的一对多关系。
实现思路
- 切入点 Subject(主题,老师讲课), Observer(观察者,学生听课);
代码
// util.js
export const Subject = function(eventName = 'default') {
this.observerList = [];
this.eventName = eventName;
this._t;
}
// 将观察者注册到目标实例中,可以理解为 observer(学生听课) -> Subject(老师讲课);
Subject.prototype.add = function(observer) {
// observerList很多学生
if(Array.isArray(observer)) {
this.observerList.push(...observer);
} else {
this.observerList.push(observer);
}
}
// 将指定观察者移除,可以理解为 observer(张三同学不来听课了);
Subject.prototype.remove = function(observer) {
const index = this.observerList.findIndex((item)=>item === observer);
if(index !== -1) {
this.observerList.splice(index,1);
return true;
} else {
return false;
}
}
// 将内容通知到所有学生(observerList)
Subject.prototype.notify = function(data){
// 防抖处理
clearTimeout(this._t);
this._t = setTimeout(() => {
const observerList = this.observerList;
for(let i=0,len=observerList.length; i < len; i++) {
//update是由observer暴露出来的接口,如果没有提供的话是无法通知的
observerList[i].update({eventName: this.eventName, data})
}
}, 500);
//这里设置为500是为了测试效果,通常设置为50
}
export const Observer = function(fn) {
const _fn = fn;
this.update = function(data) {
_fn(data)
}
}
复制代码
实际用法
//实际用法
import * as React from 'React';
import { Subject, Observer } from './util';
const subject = new Subject();
export default class ClassRoom extends React.Component {
constructor(props) {
super(props);
this.student = new Observer(this.callback);
this.student2 = new Observer(this.callback2);
subject.add([this.student,this.student2]);
console.log(subject);
}
componentWillUnmount() {
console.log(subject.remove(this.student));
console.log(subject.remove(this.student2));
console.log(subject);
}
callback = ({eventName, data}) => {
console.log('student',data);
};
callback2 = ({eventName, data}) => {
console.log('student',data);
}
teacher = () => {
subject.notify('内容...');
};
render() {
return <button onClick={this.teacher}>notify</button>
}
}
复制代码
二、发布订阅模式
概要:需要理解调度中心,订阅者,发布者之间的关系。
- 罗翔老师在网上开了一个专栏,老师把课上传到平台上,喜欢这门课的同学订阅该专栏。后续只需要老师把视频传到平台上,平台会将更新的视频推送给订阅者。
- 老师与学生的联系不是那么大了,两者之间通过平台来联系。
总结
- 发布订阅模式是观察者模式的一种变种,发布者和订阅者相互之间不知道彼此的存在,他们通过调度中心联系到彼此。事件名称一直是他们能联系彼此的条件。
- 多应用于将多层透传通信方式扁平化。
实现思路
- 主要是实现register,watcher,trigger,unregister这是个方法。整个流程的顺序也是按照这个来的。
- watcher中会包含register和unregister。
代码(常规版)
// util.js
export const pubsubFactory = function (myPubsub) {
let eventCenter = {};
let eventTimeout = {};
myPubsub.getEventCenter = function () {
return eventCenter;
};
myPubsub.watcher = function (eventName, fn) {
// 如果没有register需要先注册一下事件名,并给该事件列队增加fn
if (!eventCenter[eventName]) {
eventCenter[eventName] = [fn];
} else {
eventCenter[eventName].push(fn);
}
// unregister
return function () {
let events = eventCenter[eventName];
eventCenter[eventName] = events.filter((handler) => handler !== fn);
};
};
myPubsub.trigger = function (eventName, data) {
const events = eventCenter[eventName];
if (!events) {
console.log(eventName + '事件未注册');
return;
}
let _t = eventTimeout[eventName];
clearTimeout(_t);
eventTimeout[eventName] = setTimeout(() => {
for (let i = 0, len = events.length; i < len; i++) {
events[i]({ eventName, data });
}
}, 50);
};
return myPubsub;
};
export const pubsub = pubsubFactory({});
复制代码
实际用法
// 实际用法
import * as React from 'React';
import { pubsub } from './util';
export default class PubSubDemo extends React.Component {
constructor(props) {
super(props);
this.subject = pubsub.watcher('event', this.callback);
this.subject2 = pubsub.watcher('event2', this.callback2);
}
componentWillUnmount() {
this.subject();
this.subject2();
console.log(pubsub.getEventCenter())
}
callback = ({ eventName, data }) => {
console.log(eventName, data);
};
callback2 = ({ eventName, data }) => {
console.log(eventName, data);
};
publish = () => {
pubsub.trigger('event', 'data1');
};
publish2 = () => {
pubsub.trigger('event2','data2');
};
render() {
return (
<div>
<button onClick={this.publish}>publish</button>
<button onClick={this.publish2}>publish2</button>
</div>
);
}
}
复制代码
代码(装饰器版)
// util.js
// 该版本不支持hook组件
import * as _ from "lodash";
function decorator() {
const events = [];
let cache = undefined;
let _t = null;
const register = callback => {
if (!events.includes(callback)) {
events.push(callback);
}
};
const unregister = callback => {
const index = events.indexOf(callback);
events.splice(index, 1);
return index > -1;
};
const trigger = value => {
clearTimeout(_t);
cache = value;
_t = setTimeout(() => {
events.forEach(callback => {
_.isFunction(callback) && callback(cache);
});
}, 50);
};
const watcher = function(inst, funcName) {
const name = `__${funcName}__`;
const { componentDidMount, componentWillUnmount } = inst;
// 这里存在多级递归调用的问题,不建议一个组件绑定太多的pubsub事件。
inst.componentDidMount = function() {
this[name] = this[funcName].bind(this);
register(this[name]);
if (_.isFunction(componentDidMount)) {
componentDidMount.call(this);
}
};
inst.componentWillUnmount = function() {
unregister(this[name]);
delete this[name];
if (_.isFunction(componentWillUnmount)) {
componentWillUnmount.call(this);
}
};
};
return {
watcher,
trigger,
register,
unregister
};
}
const DF = {};
export default function decoratorFactory(name = "default") {
if (!(name in DF)) {
DF[name] = decorator();
}
return DF[name];
}
复制代码
实际用法
// 实际用法
import * as React from 'React';
import decoratorFactory from './util';
const state = decoratorFactory('event');
const state2 = decoratorFactory('event2');
export default class DecoratorPubsubDemo extends React.Component {
componentWillUnmount() {
console.log(state);
console.log(state2);
}
@state.watcher
callback = (data) => {
console.log(data);
};
@state2.watcher
callback2 = (data) => {
console.log(data);
};
publish = () => {
state.trigger('data1');
};
publish2 = () => {
state2.trigger('data2');
};
render() {
return (
<div>
<button onClick={this.publish}>publish</button>
<button onClick={this.publish2}>publish2</button>
</div>
);
}
}
复制代码
三、参考文章
四、结语
以上观点只是个人结合实践产出的理解,欢迎各位过路的大佬补充斧正~
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END