如何與 Vue 3 一起使用
本指南說明如何在 Vue 3 應用程式中使用 Socket.IO。
範例
結構
src
├── App.vue
├── components
│ ├── ConnectionManager.vue
│ ├── ConnectionState.vue
│ └── MyForm.vue
├── main.js
└── socket.js
Socket.IO 用戶端在 src/socket.js
檔案中初始化
src/socket.js
import { reactive } from "vue";
import { io } from "socket.io-client";
export const state = reactive({
connected: false,
fooEvents: [],
barEvents: []
});
// "undefined" means the URL will be computed from the `window.location` object
const URL = process.env.NODE_ENV === "production" ? undefined : "http://localhost:3000";
export const socket = io(URL);
socket.on("connect", () => {
state.connected = true;
});
socket.on("disconnect", () => {
state.connected = false;
});
socket.on("foo", (...args) => {
state.fooEvents.push(args);
});
socket.on("bar", (...args) => {
state.barEvents.push(args);
});
事件聆聽器註冊在 src/socket.js
檔案中,因為我們強烈建議不要在元件中註冊聆聽器。關於這點,請見下方。
然後你可以在元件中使用它
src/components/ConnectionState.vue
<template>
<p>State: {{ connected }}</p>
</template>
<script>
import { state } from "@/socket";
export default {
name: "ConnectionState",
computed: {
connected() {
return state.connected;
}
}
}
</script>
src/components/ConnectionManager.vue
<template>
<button @click="connect()">Connect</button>
<button @click="disconnect()">Disconnect</button>
</template>
<script>
import { socket } from "@/socket";
export default {
name: "ConnectionManager",
methods: {
connect() {
socket.connect();
},
disconnect() {
socket.disconnect();
}
}
}
</script>
socket
物件也可以在不立即連線的情況下使用 autoConnect
選項初始化
export const socket = io(URL, {
autoConnect: false
});
這在例如使用者必須在連線前提供一些憑證時很有用。
src/components/MyForm.vue
<template>
<form @submit.prevent="onSubmit">
<input v-model="value" />
<button type="submit" :disabled="isLoading">Submit</button>
</form>
</template>
<script>
import { socket } from "@/socket";
export default {
name: "MyForm",
data() {
return {
isLoading: false,
value: ""
}
},
methods: {
onSubmit() {
this.isLoading = true;
socket.timeout(5000).emit("create-something", this.value, () => {
this.isLoading = false;
});
},
}
}
</script>
參考:https://vuejs.org/guide/scaling-up/state-management.html
重要事項
這些說明對任何前端框架都有效。
熱模組重新載入
包含 Socket.IO 客戶端初始化的檔案(例如上述範例中的 src/socket.js
檔案)的熱重新載入可能會讓先前的 Socket.IO 連線保持運作,這表示
- 你可能會在 Socket.IO 伺服器上有多個連線
- 你可能會收到來自先前連線的事件
唯一已知的解決方法是在更新這個特定檔案時進行全頁重新載入(或完全停用熱重新載入,但這可能會有點極端)。
參考:https://vue-loader.vuejs.org/guide/hot-reload.html
子元件中的聆聽器
我們強烈建議不要在子元件中註冊事件聆聽器,因為這會將 UI 狀態與事件接收時間綁定在一起:如果元件未掛載,則可能會遺漏一些訊息。
src/components/MyComponent.vue
<script>
import { socket } from "@/socket";
export default {
name: "MyComponent",
data() {
return {
fooEvents: []
}
},
mounted() {
// BAD
socket.on("foo", (...args) => {
this.fooEvents.push(args);
});
}
}
</script>
不過,這在你的根元件中是沒問題的(因為它總是掛載的)。
暫時斷線
WebSocket 連線雖然功能強大,但並非總是處於連線和執行狀態
- 使用者和 Socket.IO 伺服器之間的任何事物都可能遭遇暫時故障或重新啟動
- 伺服器本身可能會在自動調整規模政策下被終止
- 使用者可能會斷線或在行動瀏覽器的狀況下從 Wi-Fi 切換到 4G。
這表示你需要適當處理暫時斷線,才能為你的使用者提供絕佳體驗。
好消息是,Socket.IO 包含一些可以協助你的功能。請查看
使用 Pinia
Pinia 是 Vue 的儲存庫程式庫,它允許你在元件/頁面之間共用狀態。
可以在 這裡 找到更多資訊。
Pinia 的儲存庫和 Socket.IO 連線可以使用下列模式同步
import { defineStore } from "pinia";
import { socket } from "@/socket";
export const useItemStore = defineStore("item", {
state: () => ({
items: [],
}),
actions: {
bindEvents() {
// sync the list of items upon connection
socket.on("connect", () => {
socket.emit("item:list", (res) => {
this.items = res.data;
});
});
// update the store when an item was created
socket.on("item:created", (item) => {
this.items.push(item);
});
},
createItem(label) {
const item = {
id: Date.now(), // temporary ID for v-for key
label
};
this.items.push(item);
socket.emit("item:create", { label }, (res) => {
item.id = res.data;
});
},
},
});
import { defineStore } from "pinia";
import { socket } from "@/socket";
export const useConnectionStore = defineStore("connection", {
state: () => ({
isConnected: false,
}),
actions: {
bindEvents() {
socket.on("connect", () => {
this.isConnected = true;
});
socket.on("disconnect", () => {
this.isConnected = false;
});
},
connect() {
socket.connect();
}
},
});
然後在你的根元件中
<script setup>
import { useItemStore } from "@/stores/item";
import { useConnectionStore } from "@/stores/connection";
import { socket } from "@/socket";
const itemStore = useItemStore();
const connectionStore = useConnectionStore();
// remove any existing listeners (after a hot module replacement)
socket.off();
itemStore.bindEvents();
connectionStore.bindEvents();
</script>