已經很習慣串接 restful api,突然有個客戶說他的遊戲網站所有資料傳輸都是用 websocket,也就說一般我們打 api,會有開始送出 request 、結束收到 response,是一個有時間性的過程;但是如果是 websocket 的話,送出跟收到是無法放在同一個 function 裡等待(確保)他完成的。websocket 一但連線並開始監聽,就會隨時收到來自 server 的訊息,並不一定能代表這個回傳結果一定就是來自你剛剛送出的請求

菜鳥如我上網找了很多方法,然後又因為不想要在每個頁面都要寫一次監聽的 function,所以我就異想天開把監聽的步驟放在App.vue,然後只要收到資訊就把資料送到 vuex,在每個頁面去 watch vuex state 的變化~(雖然這樣好像還是要監聽,但是總覺得這樣寫法比較 vue 一點?XD)

 

首先,要先有websocket連線的底層。

建立一個檔案叫做socket.js

import Vue from "vue";
const wsUrl = process.env.VUE_APP_API_URL;
var socket = new WebSocket(wsUrl);
const emitter = new Vue({
  methods: {
    send(message) {
      if (1 === socket.readyState) socket.send(JSON.stringify(message));
    },
    connect() {
      socket = new WebSocket(wsUrl);
      socket.onmessage = function(msg) {
        emitter.$emit("message", msg.data);
      };
      socket.onerror = function(err) {
        emitter.$emit("error", err);
      };
      socket.onclose = function() {
        emitter.connect();
      };
    }
  }
});

emitter.connect();
export default emitter;

emitter這個東西是方便之後我們在vue裡面傳送資料給 server 的寫法。

然後因為我遇到的是只要登出後,後端會自己把我的websocket連線斷開,導致我不刷新頁面的話,就無法打傳送資料,所以在onclose那邊才會又重新連線。

 

 

vuex 中準備一個 state 去接 websocket 的資料

新增一個檔案 store/ws.js,記得要在store/index.js裡面引用進去。

export const state = {
  wsRes: {}
};
export const actions = {};

export const mutations = {
  setWsRes(state, payload) {
    state.wsRes = payload;
  }
};
export const getters = {};

export default {
  state,
  actions,
  mutations,
  getters,
  namespaced: true
};
import Vue from "vue";
import Vuex from "vuex";
import ws from "./ws";
Vue.use(Vuex);

export default new Vuex.Store({
  state: {},
  mutations: {},
  actions: {},
  modules: { ws }
});

 

 

App.vue中全局監聽websocket,並且收到資料後馬上傳入vuex

<template>
  <div id="app">
    <router-view />
  </div>
</template>
<script>
import Socket from "@/utils/socket";
import { mapMutations } from "vuex";
export default {
  name: "App",
  created() {
    Socket.$on("message", this.handleGetMessage);
  },
  beforeDestroy() {
    Socket.$off("message", this.handleGetMessage);
  },
  methods: {
    ...mapMutations({
      setWsRes: "ws/setWsRes",
    }),
    handleGetMessage(msg) {
      // 一些全局的動作可以放在這裡
      this.setWsRes(JSON.parse(msg));
    }
  }
};
</script>

如果你有一些比如收到錯誤碼要登出這種全局的東西,就可以寫在handleGetMessage裡面~這樣就不用在每個vue頁面去判斷。

 

 

重頭戲:在頁面中使用

<template>
  <div>{{ name }}</div>
</template>
<script>
import { mapState } from "vuex";
import Socket from "@/utils/socket";
export default {
  name: "Home",
  data() {
    return {
      name: "",
    };
  },
  computed: {
    ...mapState({
      wsRes: state => state.ws.wsRes
    }),
  },
  watch: {
    wsRes(val) {
      const { StatusCode, name } = val;
      StatusCode === 0 && this.$message({ type: "error", message: "查詢失敗" });
      if (list) {
        this.name = name;
      }
    }
  },
};
</script>

這樣就可以隨時檢查是否後端有傳新的name,然後直接顯示在畫面上。

如果是要傳送資料的話,要使用send方法:

methods: {
  handleGetName() {
    Socket.send(
      //...一些後端要求要傳的資料request,通常會是一包物件{}。
    );
  }
}

沒錯!真的這樣就可以了,因為上面我們已經用watch監聽結果了,這邊就只要專注在送出資料就可。

0
0 回復

發表評論

想要加入討論嗎?
請盡情發表您的想法!

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。