有一个项目,今年12月份开始重构,项目涉及到了socket。但是socket用的是以前一个开发人员封装的包(这个一直被当前的成员吐槽为什么不用已经千锤百炼的轮子)。因此,趁着这个重构的机会,将vue-socket.io引入,后端就用socket.io。我也好奇看了看vue-socket.io的源码(我不会说是因为这个库的文档实在太简略了,我为了稳点去看源码了解该怎么用)
开始
文件架构
我们主要看src下的三个文件,可以看出该库是用了观察者模式
Main.js
// 这里创建一个observe对象,具体做了什么可以看Observer.js文件 let observer = new Observer(connection, store) // 将socket挂载到了vue的原型上,然后就可以 // 在vue实例中就可以this.$socket.emit('xxx', {}) Vue.prototype.$socket = observer.Socket;
import store from './yourstore' Vue.use(VueSocketio, socketio('http://socketserver.com:1923'), store);
我们如果要使用这个库的时候,一般是这样写的代码(上图2)。上图一的connection和store就分别是图二的后两个参数。意思分别为socket连接的url和vuex的store啦。图一就是将这两个参数传进Observer,新建了一个observe对象,然后将observe对象的socket属性挂载在Vue原型上。那么我们在Vue的实例中就可以直接 this.$sockets.emit('xxx', {})了
// 👇就是在vue实例的生命周期做一些操作 Vue.mixin({ created(){ let sockets = this.$options['sockets'] this.$options.sockets = new Proxy({}, { set: (target, key, value) => { Emitter.addListener(key, value, this) target[key] = value return true; }, deleteProperty: (target, key) => { Emitter.removeListener(key, this.$options.sockets[key], this) delete target.key; return true } }) if(sockets){ Object.keys(sockets).forEach((key) => { this.$options.sockets[key] = sockets[key]; }); } }, /** * 在beforeDestroy的时候,将在created时监听好的socket事件,全部取消监听 * delete this.$option.sockets的某个属性时,就会将取消该信号的监听 */ beforeDestroy(){ let sockets = this.$options['sockets'] if(sockets){ Object.keys(sockets).forEach((key) => { delete this.$options.sockets[key] }); } }
下面就是在Vue实例的生命周期做一些操作。创建的时候,将实例中的$options.sockets的值先缓存下来,再将$options.sockets指向一个proxy对象,这个proxy对象会拦截外界对它的赋值和删除属性操作。这里赋值的时候,键就是socket事件,值就是回调函数。赋值时,就会监听该事件,然后将回调函数,放进该socket事件对应的回调数组里。删除时,就是取消监听该事件了,将赋值时压进回调数组的那个回调函数,删除,表示,我不监听了。这样写法,其实就跟vue的响应式一个道理。也因此,我们就可以动态地添加和移除监听socket事件了,比如this.$option.sockets.xxx = () => ()和 delete this.$option.sockets.xxx。最后将缓存的值,依次赋值回去,那么如下图的写法就会监听到事件并执行回调函数了:
var vm = new Vue({ sockets:{ connect: function(){ console.log('socket connected') }, customEmit: function(val){ console.log('this method was fired by the socket server. eg: io.emit("customEmit", data)') } }, methods: { clickButton: function(val){ // $socket is socket.io-client instance this.$socket.emit('emit_method', val); } } })
Emitter.js
Emitter.js主要是写了一个Emitter对象,该对象提供了三个方法:
addListener
addListener(label, callback, vm) { // 回调函数类型是回调函数才对 if(typeof callback == 'function'){ // 这里就很常见的写法了,判断map中是否已经注册过该事件了 // 如果没有,就初始化该事件映射的值为空数组,方便以后直接存入回调函数 // 反之,直接将回调函数放入数组即可 this.listeners.has(label) || this.listeners.set(label, []); this.listeners.get(label).push({callback: callback, vm: vm}); return true } return false }
其实很常规啦,实现发布订阅者模式或者观察者模式代码的同学都很清楚这段代码的意思。Emiiter用一个map来存储事件以及它对应的回调事件数组。这段代码先判断map中是否之前已经存储过了该事件,如果没有,初始化该事件对应的值为空数组,然后将当前的回调函数,压进去,反之,直接压进去。
removeListener
if (listeners && listeners.length) { index = listeners.reduce((i, listener, index) => { return (typeof listener.callback == 'function' && listener.callback === callback && listener.vm == vm) ? i = index : i; }, -1); if (index > -1) { listeners.splice(index, 1); this.listeners.set(label, listeners); return true; } } return false;