如何在 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);
});
在开发过程中,您需要在服务器上启用 CORS
const io = new Server({
cors: {
origin: "http://localhost:8080"
}
});
参考:处理 CORS
事件监听器在 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.ac.cn/guide/scaling-up/state-management.html
重要说明
这些说明适用于任何前端框架。
热模块重载
包含 Socket.IO 客户端初始化的文件(例如,上面示例中的 src/socket.js
文件)的热重载可能会使之前的 Socket.IO 连接保持活动状态,这意味着
- 您的 Socket.IO 服务器上可能存在多个连接
- 您可能会收到来自先前连接的事件
唯一已知的解决方法是在更新此特定文件时执行 **完整页面重新加载**(或完全禁用热重载,但这可能有点极端)。
参考:https://vue-loader.vuejs.ac.cn/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>