項目是一個單頁面web應用,有一個基礎的websocket服務,用於和服務器通訊。剛開始主要有兩個做用:javascript
帳號防重複登錄。當帳號在另外的地方登錄時,websocket收到服務器消息,網站即時彈窗提示用戶「當前帳號在另一個地方登錄」,而後網站清除登陸信息並跳轉到登陸頁面。java
當打開送貨管理界面時,如有新的送貨請求,websocket收到服務器消息,即時提示用戶「您有新的送貨請求,請及時處理」。若沒有打開送貨管理界面,不提示。web
起初是這樣的實現的:緩存
//BasciWebsocket.js websocket.onmessage = function (event) { try{ let {data} = event; let _data = JSON.parse(data); switch(_data.type){ case 100: Modal.warning({ title: '帳號登陸提醒', content: "您的帳號已經在另外的地方進行登錄,請確認是否您本人操做。若有異常,請聯繫公司系統管理員。", okText: '知道了', onOk: ()=> { tool.clearUserCookie(); window.location = "/login"; } }); break; case 101: let isPageShow = tool.ifDeliveryPageShow(); if(isPageShow){ //提示「您有新的送貨請求,請及時處理」 } break; } }catch(e){ } };
看起來並無什麼問題。可是,隨着項目愈來愈複雜,websocket負責通知的消息也愈來愈豐富。接收到消息後,每每還要改變具體的頁面展示,跟具體的頁面的關聯性很是大。好比,用戶停留在訂單詳情頁時,若是收到新的訂單反饋,那麼訂單詳情頁的評論列表區域就要動畫出現新的反饋信息。服務器
這個時候,你會發現,把這類跟具體頁面強關聯的邏輯寫在基礎的BasciWebsocket.js
文件裏不科學,BasciWebsocket.js
也變得很臃腫,維護起來有些費勁。問題就在於基礎的websocket服務與每一個頁面的消息處理邏輯不夠解耦。websocket
不難看出,在這個業務場景裏,websocket服務其實就是一個消息發佈者,而這些具體頁面則是消息訂閱者。很天然地,我想到了一根救命稻草——經典的發佈-訂閱模式(又叫觀察者模式)。socket
觀察者模式定義對象間的一種一對多的依賴關係,當一個對象的狀態發生改變時,全部依賴它的對象都將獲得通知。函數
如何實現觀察者模式?動畫
指定好誰充當發佈者(websocket服務);網站
爲發佈者添加一個緩存列表,用於存放回調函數以便通知訂閱者(具體頁面);
發佈者對外提供消息訂閱/退訂方法,實質就是改變緩存列表的內容;
發佈消息時,發佈者會遍歷這個緩存列表,一次觸發裏面存放的訂閱者回調函數;
首先,把BasciWebsocket.js
變身爲消息發佈者:
export default class BasicWebsocket{ constructor() { if(typeof BasicWebsocket.instance === 'object') { return BasicWebsocket.instance; } //緩存列表 this.listeners = {}; this.init(); BasicWebsocket.instance = this; } //消息訂閱 addListener = (key,func)=>{ this.listeners[key] = func; }; //取消訂閱 removeListener = (key)=>{ delete this.listeners[key]; }; init = ()=>{ //獲取websocket對象,全局惟一 let websocket = this.getWebsocket(); let that = this; //監聽websocket消息 websocket.onmessage = function (event) { try{ let {data} = event let _data = JSON.parse(data); //遍歷緩存列表, 通知消息訂閱者 let keys = Object.keys(that.listeners); for(let i = 0,j = keys.length; i < j; i++){ let func = that.listeners[keys[i]]; func(_data); } }catch(e){ } }; }; //其餘代碼 }
而後,在發貨管理頁加載時,能夠進行發貨消息的訂閱;發貨管理頁卸載時,進行消息退訂。其餘頁面如此類推。
class Distribution extends Component{ // 構造 constructor(props) { super(props); // 初始狀態 this.state = { newCount:0 }; this.websocket = new BasicWebsocket(); } //組件加載完畢 componentDidMount() { let that = this; //消息訂閱 this.websocket.addListener("distribution",(d)=>{ if(d.type == 101){ that.showNotification(); //提示「您有新的送貨請求,請及時處理」 //在頁面展現新的發貨請求數量 let {count} = d.data; let count = that.state.newCount + count; that.setState({ newCount:count }); } }); } //組件即將卸載 componentWillUnmount() { //消息退訂 this.websocket.removeListener("distribution"); } }
至此,經過觀察者模式實現了基礎websocket服務和具體頁面邏輯的解耦,代碼可維護性獲得提升。