Vue合理配置WebSocket並實現羣聊⛄

學習的動力源於興趣,願你在學習新知識時,動力源於興趣而並不是其它😉javascript

前言

寫JQuery項目時,使用websocket很簡單,不用去考慮模塊化,組件之間的訪問問題,面向文檔編程便可,在Vue項目中使用時,遠遠沒有想象中的那麼簡單,須要考慮不少場景,本篇文章將與各位開發者分享下vue-native-websocket庫的使用以及配置,用其實現羣聊功能。先看下最終實現的效果🤒html

安裝依賴

本文中對於vue-native-websocket庫的講解,項目中配置了vuex,對其不瞭解的開發者請移步官方文檔,若是選擇繼續閱讀本篇文章會比較吃力。vue

  • vue-native-websocket安裝
# yarn | npm 安裝
yarn add vue-native-websocket | npm install vue-native-websocket --save
複製代碼
  • 安裝成功

配置插件

  • main.js中進行導入
import VueNativeSock from 'vue-native-websocket'
複製代碼
  • 使用VueNativeSock插件,並進行相關配置
// main.js
// base.lkWebSocket爲你服務端websocket地址
Vue.use(VueNativeSock,base.lkWebSocket,{
  // 啓用Vuex集成,store的值爲你的vuex
  store: store,
  // 數據發送/接收使用使用json格式
  format: "json",
  // 開啓自動重連
  reconnection: true,
  // 嘗試重連的次數
  reconnectionAttempts: 5,
  // 重連間隔時間
  reconnectionDelay: 3000,
  // 將數據進行序列化,因爲啓用了json格式的數據傳輸這裏須要進行重寫
  passToStoreHandler: function (eventName, event) {
    if (!eventName.startsWith('SOCKET_')) { return }
    let method = 'commit';
    let target = eventName.toUpperCase();
    let msg = event;
    if (this.format === 'json' && event.data) {
      msg = JSON.parse(event.data);
      if (msg.mutation) {
        target = [msg.namespace || '', msg.mutation].filter((e) => !!e).join('/');
      } else if (msg.action) {
        method = 'dispatch';
        target = [msg.namespace || '', msg.action].filter((e) => !!e).join('/');
      }
    }
    this.store[method](target, msg);
    this.store.state.socket.message = msg;
  }
});
複製代碼
  • vuex的相關配置:mutations和actions添加相關函數
// vuex配置文件
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    token:"",
    userID:"",
    // 用戶頭像
    profilePicture: "",
    socket: {
      // 鏈接狀態
      isConnected: false,
      // 消息內容
      message: '',
      // 從新鏈接錯誤
      reconnectError: false
    }
  },
  mutations: {
    SOCKET_ONOPEN (state, event)  {
      // 鏈接打開觸發的函數
      Vue.prototype.$socket = event.currentTarget;
      state.socket.isConnected = true
    },
    SOCKET_ONCLOSE (state, event)  {
      // 鏈接關閉觸發的函數
      state.socket.isConnected = false;
      console.log(event);
    },
    SOCKET_ONERROR (state, event)  {
      // 鏈接發生錯誤觸發的函數
      console.error(state, event)
    },
    SOCKET_ONMESSAGE (state, message)  {
      // 收到消息時觸發的函數
      state.socket.message = message
    },
    SOCKET_RECONNECT(state, count) {
      // 從新鏈接觸發的函數
      console.info(state, count)
    },
    SOCKET_RECONNECT_ERROR(state) {
      // 從新鏈接失敗觸發的函數
      state.socket.reconnectError = true;
    },
  },
  actions: {
    customerAdded (context) {
      // 新鏈接添加函數
      console.log('action received: customerAdded');
      console.log(context)
    }
  },
  modules: {
  }
})

複製代碼

至此vue-native-websocket配置結束,如需瞭解更多配置方法,請移步npm倉庫java

使用插件並實現羣聊

  • 在消息發送接收組件中添加onmessage監聽(mounted生命週期中)
// 監聽消息接收
this.$options.sockets.onmessage = (res)=>{
    // res.data爲服務端返回的數據
    const data = JSON.parse(res.data);
    // 200爲服務端鏈接創建成功時返回的狀態碼(此處根據真實後端返回值進行相應的修改)
    if(data.code===200){
        // 鏈接創建成功
        console.log(data.msg);
    }else{
        // 獲取服務端推送的消息
        const msgObj = {
            msg: data.msg,
            avatarSrc: data.avatarSrc,
            userID: data.userID
        };
        // 渲染頁面:若是msgArray存在則轉json
        if(lodash.isEmpty(localStorage.getItem("msgArray"))){
            this.renderPage([],msgObj,0);
        }else{
            this.renderPage(JSON.parse(localStorage.getItem("msgArray")),msgObj,0);
        }
    }
};
複製代碼
  • 實現消息發送
// 消息發送函數
sendMessage: function (event) {
    if (event.keyCode === 13) {
        // 阻止編輯框默認生成div事件
        event.preventDefault();
        let msgText = "";
        // 獲取輸入框下的全部子元素
        let allNodes = event.target.childNodes;
        for(let item of allNodes){
            // 判斷當前元素是否爲img元素
            if(item.nodeName==="IMG"){
                msgText += `/${item.alt}/`;
            }
            else{
                // 獲取text節點的值
                if(item.nodeValue!==null){
                    msgText += item.nodeValue;
                }
            }
        }
        // 消息發送: 消息內容、狀態碼、當前登陸用戶的頭像地址、用戶id
        this.$socket.sendObj({msg: msgText,code: 0,avatarSrc: this.$store.state.profilePicture,userID: this.$store.state.userID});
        // 清空輸入框中的內容
        event.target.innerHTML = "";
    }
}
複製代碼
  • 實現頁面渲染
// 渲染頁面函數
renderPage: function(msgArray,msgObj,status){
    if(status===1){
        // 頁面第一次加載,若是本地存儲中有數據則渲染至頁面
        let msgArray = [];
        if(localStorage.getItem("msgArray")!==null){
            msgArray = JSON.parse(localStorage.getItem("msgArray"));
            for (let i = 0; i<msgArray.length;i++){
                const thisSenderMessageObj = {
                    "msgText": msgArray[i].msg,
                    "msgId": i,
                    "avatarSrc": msgArray[i].avatarSrc,
                    "userID": msgArray[i].userID
                };
                // 解析並渲染
                this.messageParsing(thisSenderMessageObj);
            }
        }
    }else{
        // 判斷本地存儲中是否有數據
        if(localStorage.getItem("msgArray")===null){
            // 新增記錄
            msgArray.push(msgObj);
            localStorage.setItem("msgArray",JSON.stringify(msgArray));
            for (let i = 0; i <msgArray.length; i++){
                const thisSenderMessageObj = {
                    "msgText": msgArray[i].msg,
                    "msgId": i,
                    "avatarSrc": msgArray[i].avatarSrc,
                    "userID": msgArray[i].userID,
                };
                // 解析並渲染
                this.messageParsing(thisSenderMessageObj);
            }
        }else{
            // 更新記錄
            msgArray = JSON.parse(localStorage.getItem("msgArray"));
            msgArray.push(msgObj);
            localStorage.setItem("msgArray",JSON.stringify(msgArray));
            const thisSenderMessageObj = {
                "msgText": msgObj.msg,
                "msgId": Date.now(),
                "avatarSrc": msgObj.avatarSrc,
                "userID": msgObj.userID
            };
            // 解析並渲染
            this.messageParsing(thisSenderMessageObj);
        }
    }
}
複製代碼
  • 實現消息解析
// 消息解析
messageParsing: function(msgObj){
    // 解析接口返回的數據進行渲染
    let separateReg = /(\/[^/]+\/)/g;
    let msgText = msgObj.msgText;
    let finalMsgText = "";
    // 將符合條件的字符串放到數組裏
    const resultArray = msgText.match(separateReg);
    if(resultArray!==null){
        for (let item of resultArray){
            // 刪除字符串中的/符號
            item = item.replace(/\//g,"");
            for (let emojiItem of this.emojiList){
                // 判斷捕獲到的字符串與配置文件中的字符串是否相同
                if(emojiItem.info === item){
                    const imgSrc = require(`../assets/img/emoji/${emojiItem.hover}`);
                    const imgTag = `<img src="${imgSrc}" width="28" height="28" alt="${item}">`;
                    // 替換匹配的字符串爲img標籤:全局替換
                    msgText = msgText.replace(new RegExp(`/${item}/`,'g'),imgTag);
                }
            }
        }
        finalMsgText = msgText;
    }else{
        finalMsgText = msgText;
    }
    msgObj.msgText = finalMsgText;
    // 渲染頁面
    this.senderMessageList.push(msgObj);
    // 修改滾動條位置
    this.$nextTick(function () {
        this.$refs.messagesContainer.scrollTop = this.$refs.messagesContainer.scrollHeight;
    });
}
複製代碼
  • DOM結構

經過每條消息的userID和vuex中的存儲的當前用戶的userID來判斷當前消息是否爲對方發送node

<!--消息顯示-->
<div class="messages-panel" ref="messagesContainer">
    <div class="row-panel" v-for="item in senderMessageList" :key="item.msgId">
        <!--發送者消息樣式-->
        <div class="sender-panel" v-if="item.userID===userID">
            <!--消息-->
            <div class="msg-body">
                <!--消息尾巴-->
                <div class="tail-panel">
                    <svg class="icon" aria-hidden="true">
                        <use xlink:href="#icon-zbds30duihuakuangyou"></use>
                    </svg>
                </div>
                <!--消息內容-->
                <p v-html="item.msgText"/>
            </div>
            <!--頭像-->
            <div class="avatar-panel">
                <img :src="item.avatarSrc" alt="">
            </div>
        </div>
        <!--對方消息樣式-->
        <div class="otherSide-panel" v-else>
            <!--頭像-->
            <div class="avatar-panel">
                <img :src="item.avatarSrc" alt="">
            </div>
            <!--消息-->
            <div class="msg-body">
                <!--消息尾巴-->
                <div class="tail-panel">
                    <svg class="icon" aria-hidden="true">
                        <use xlink:href="#icon-zbds30duihuakuangzuo"></use>
                    </svg>
                </div>
                <!--消息內容-->
                <p v-html="item.msgText"/>
            </div>
        </div>
    </div>
</div>
複製代碼

羣聊實現思路解析

  • 消息組件掛載完成後:從本地存儲中讀取消息記錄,若是存在則將消息渲染至頁面
  • 監聽消息接收:服務端推送消息後觸發onmessage事件
  • 獲取到服務端推送的消息後:從本地存儲中讀取消息記錄
  • 若是本地存儲中存在消息記錄:更新本地存儲中對消息記錄,將當前消息對象放進消息記錄中,並渲染頁面
  • 若是本地存儲中不存在消息記錄:在本地存儲中建立消息記錄字段,將當前消息對象放進消息記錄中,並渲染頁面
  • 觸發消息發送:使用this.$socket.sendObj方法,傳當前用戶的相關信息,推送至服務端websocket服務
  • 服務端收到消息後:將當前用戶發送的消息進行處理,併發送給與服務器取得鏈接的客戶端。
  • 客戶端收到消息後:觸發onmessage事件

寫在最後

  • 文中若有錯誤,歡迎在評論區指正,若是這篇文章幫到了你,歡迎點贊和關注😊
  • 本文首發於掘金,如需轉載請評論區留言💌
相關文章
相關標籤/搜索