ai問答:vue3+pinia+WebSocket 封裝斷線重連(實戰)

讓夢想縱橫發表於2023-05-08

socket例項 掛載到全域性

為方便梳理,請忽略typescript

# main.ts
import {createApp} from 'vue'

import App from './App.vue'
import {socket} from "@/xihu/socket"
import router from "@/xihu/router"

const app = createApp(App);
app.use(router).mount('#root');

// 全域性掛載
app.config.globalProperties.$socket = socket;

Socket封裝(斷線重連)

這個WebSocket類封裝了WebSocket的連線、重連、傳送資料等方法。
connect方法中,它會連線WebSocket,並繫結相關事件監聽。
onclose事件中,它會呼叫reconnect方法進行重連。
reconnect方法會在一定時間內重連,並且重連的時間間隔會越來越長,最大重連次數達到設定值後就不再重連。
這樣就實現了一個可以斷線重連的WebSocket連線。
我們可以在vue應用中使用這個類來進行WebSocket通訊,並處理可能出現的網路斷開重連情況。

# socket.ts

// @ts-nocheck
export default class Socket {
    constructor(url, protocols) {
        this.url = url
        this.protocols = protocols
        this.ws = null
        this.reconnectTimeout = 1000
        this.maxReconnectTimes = 5
    }

    connect() {
        this.ws = new WebSocket(this.url, this.protocols)
        this.ws.onopen = () => {
            console.log('WebSocket連線成功')
            this.reconnectTimes = 0
        }
        this.ws.onclose = () => {
            console.log('WebSocket斷開連線')
            this.reconnect()
        }
        this.ws.onerror = err => {
            console.log('WebSocket連線出錯', err)
        }
    }

    reconnect() {
        if (this.reconnectTimes < this.maxReconnectTimes) {
            setTimeout(() => {
                this.connect()
                this.reconnectTimes++
            }, this.reconnectTimeout)
            this.reconnectTimeout *= 2
        } else {
            console.log('WebSocket重連超過最大次數,放棄重連')
        }
    }

    // 訊息傳送
    msg(param) {
        if (param === 'heartbeat') {
            this.ws.send(param);
        } else {
            this.ws.send(JSON.stringify(param));
        }
    }

    // 延遲傳送
    timeout(param) {
        setTimeout(() => {
            this.msg(param);
        }, 2000)
    }

    send(param) {
        if (this.ws.readyState === this.ws.OPEN) {
            this.msg(param);
        } else if (this.ws.readyState === this.ws.CONNECTING) {
            this.timeout(param);
        } else {
            this.timeout(param);
        }
    }
}

例項化socket

透過type關鍵字,分發資料,並且透過pinia(vuex)儲存實時資料
在訊息回撥函式,處理返回的資料,使用type關鍵字對應各種推送事件,比如:實時裝置告警、地圖顯示使用者座標等等...

// @ts-nocheck
import {createPinia} from 'pinia';
import {useAlarm} from '@/store/alarm';

// 狀態管理
export const pinia = createPinia();
export const store = useAlarm(pinia);

export function wsInit(callback) {
    const url = 'ws://api.xx.cn';
    const init = new Socket(url);

    // 連線 WebSocket
    init.connect();

    // 監聽 WebSocket
    init.ws.onmessage = function (ev) {
        if (ev && ev.data && ev.data.indexOf('subscribe') > -1) {
            console.log('subscribe->', ev.data);
        } else if (ev && ev.data) {
            var data = eval('(' + ev.data + ')');
            callback(data);
        }
    };

    return init;
}

// 訊息回撥
export const socket = wsInit((data) => {
    switch (data.type) {
        case 1:
            store.setType1(data);
            break;
        case 2:
            store.setType2(data.message);
            break;
    }
});

// 心跳連線
function heartbeat() {
    socket.send("heartbeat");
}

// 十分鐘一次 (簡陋心跳,也請忽略吧^_^)
heartbeat();
setInterval(heartbeat, 1000 * 60 * 10);

狀態管理

import {defineStore} from 'pinia'
export const useAlarm = defineStore('user', {
    state:()=>({
        type1:{},
        type2:{},
    }),
    getters:{
        getType1: (state) => state.type1,
        getType2: (state) => state.type2,
    },
    actions:{
        setType1(payload: any) {
            this.type1 = payload;
        },
        setType2(payload: any) {
            this.type2 = payload;
        },
    },
})

在頁面中,使用資料(pinia)

import { watch, computed, onMounted, getCurrentInstance} from 'vue'
import {useAlarm} from "@/xihu/store/alarm";
const store = useAlarm();

// 還記得全域性掛載的`$socket`吧,這樣使用
const ctx: any = getCurrentInstance();
const {$socket} = ctx.appContext.config.globalProperties;

onMounted(() => {
    // 列表資料 -- 透過給`websocket`傳送`{"cmd":"1"}`實現資料互動
    $socket.send({cmd: 1});
});

const click = ()=>{
    // 其他資料 -- 點選列表的某一項,根據引數獲取資料
    $socket.send({cmd: 2,extras:{id:123}});
}

// 第一種 監聽方式:
const type1 = computed(() => store.type1);
watch(type1, (message: any) => {
    console.log('/computed/watch/', message);
}, {deep: true});

// 第二種 監聽方式:
store.$subscribe(({events}: any, state) => {
    if (events.key === 'type1') {
        console.log('/$subscribe/', state.type1);
    }
});

大多數情況,資料是後臺主動推送的,比如:告警資料,這也是使用websocket的主要原因

相關文章