useSocket WebSocket 使用说明
项目地址
- Node.js 服务端项目 : https://gitee.com/node-server_yarn/node_test_learn.git
- UniApp 客户端项目 : https://gitee.com/uniapp_yarn/uniapp_socket_task.git
概述
useSocket
是一个基于 Vue 3 Composition API 的 WebSocket 封装 Hook,专为 UniApp 项目设计。它提供了完整的 WebSocket 连接管理、自动重连、心跳检测等功能,让您能够轻松地在 UniApp 项目中使用 WebSocket 进行实时通信。
功能特性
- ✅ 自动连接管理 - 支持自动连接和手动连接
- ✅ 自动重连机制 - 连接断开时自动重连,可配置重连次数和间隔
- ✅ 心跳检测 - 定期发送心跳包保持连接活跃
- ✅ 消息管理 - 自动解析和存储接收到的消息
- ✅ 状态监控 - 实时监控连接状态和重连次数
- ✅ 生命周期管理 - 组件卸载时自动断开连接
- ✅ 错误处理 - 完善的错误处理和回调机制
安装和导入
1. 克隆项目
首先克隆相关项目到本地:
bash
# 克隆 Node.js 服务端项目
git clone https://gitee.com/node-server_yarn/node_test_learn.git
# 克隆 UniApp 客户端项目
git clone https://gitee.com/uniapp_yarn/uniapp_socket_task.git
2. 文件位置
将 useSocket.js
文件放置在项目的 src/hooks/
目录下。
3. 导入方式
javascript
import {useSocket} from '@/hooks/useSocket';
基本使用
1. 最简单的使用方式
javascript
<template>
<view>
<text>连接状态: {{ isConnected ? '已连接' : '未连接' }}</text>
<button @click="sendMessage">发送消息</button>
</view>
</template>
<script setup>
import { useSocket } from '@/hooks/useSocket';
// 创建 WebSocket 连接
const { isConnected, send, messages } = useSocket({
url: 'ws://localhost:8080/ws',
onMessage: (message) => {
console.log('收到消息:', message);
}
});
// 发送消息
const sendMessage = () => {
send({
type: 'text',
content: 'Hello WebSocket!'
});
};
</script>
2. 完整配置示例
javascript
<template>
<view class="chat-container">
<!-- 连接状态显示 -->
<view class="status-bar">
<text>状态: {{ isConnected ? '已连接' : '未连接' }}</text>
<text v-if="reconnectCount > 0">重连次数: {{ reconnectCount }}</text>
</view>
<!-- 消息列表 -->
<scroll-view class="message-list" scroll-y>
<view v-for="message in messages" :key="message.timestamp" class="message-item">
<text>{{ message.content }}</text>
</view>
</scroll-view>
<!-- 输入框 -->
<view class="input-area">
<input v-model="inputText" placeholder="输入消息..." @confirm="handleSend" />
<button @click="handleSend">发送</button>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue';
import { useSocket } from '@/hooks/useSocket';
const inputText = ref('');
// 创建 WebSocket 连接
const {
isConnected,
reconnectCount,
messages,
connect,
disconnect,
send,
clearMessages
} = useSocket({
// WebSocket 服务器地址
url: 'ws://localhost:8080/ws',
// 最大重连次数
maxReconnectCount: 5,
// 重连间隔(毫秒)
reconnectInterval: 3000,
// 是否自动连接
autoConnect: true,
// 心跳间隔(毫秒)
heartBeatInterval: 5000,
// 连接成功回调
onOpen: () => {
console.log('WebSocket 连接成功');
uni.showToast({
title: '连接成功',
icon: 'success'
});
},
// 接收消息回调
onMessage: (message) => {
console.log('收到消息:', message);
// 可以在这里处理不同类型的消息
if (message.type === 'notification') {
uni.showToast({
title: message.content,
icon: 'none'
});
}
},
// 连接错误回调
onError: (error) => {
console.error('WebSocket 错误:', error);
uni.showToast({
title: '连接错误',
icon: 'error'
});
},
// 连接关闭回调
onClose: (event) => {
console.log('WebSocket 连接关闭:', event);
},
// 达到最大重连次数回调
onMaxReconnect: () => {
console.log('已达到最大重连次数');
uni.showModal({
title: '连接失败',
content: '无法连接到服务器,请检查网络连接',
showCancel: false
});
}
});
// 发送消息
const handleSend = async () => {
if (!inputText.value.trim()) return;
try {
await send({
type: 'text',
content: inputText.value.trim(),
timestamp: Date.now()
});
inputText.value = '';
console.log('消息发送成功');
} catch (error) {
console.error('发送失败:', error);
uni.showToast({
title: '发送失败',
icon: 'error'
});
}
};
// 手动连接
const handleConnect = () => {
connect();
};
// 手动断开连接
const handleDisconnect = () => {
disconnect();
};
// 清空消息
const handleClearMessages = () => {
clearMessages();
};
</script>
API 参考
useSocket(options)
参数 (options)
参数名 | 类型 | 默认值 | 说明 |
---|---|---|---|
url |
string | '' |
WebSocket 服务器地址 |
maxReconnectCount |
number | 5 |
最大重连次数 |
reconnectInterval |
number | 3000 |
重连间隔(毫秒) |
autoConnect |
boolean | true |
是否自动连接 |
heartBeatInterval |
number | 5000 |
心跳间隔(毫秒) |
onOpen |
function | - | 连接成功回调 |
onMessage |
function | - | 接收消息回调 |
onError |
function | - | 连接错误回调 |
onClose |
function | - | 连接关闭回调 |
onMaxReconnect |
function | - | 达到最大重连次数回调 |
返回值
属性名 | 类型 | 说明 |
---|---|---|
isConnected |
ref(boolean) | 连接状态 |
reconnectCount |
ref(number) | 当前重连次数 |
messages |
ref(Array) | 消息列表 |
connect |
function | 手动连接方法 |
disconnect |
function | 断开连接方法 |
send |
function | 发送消息方法 |
clearMessages |
function | 清空消息方法 |
方法详解
connect()
手动建立 WebSocket 连接。
javascript
const {connect} = useSocket({url: 'ws://localhost:8080/ws'});
connect();
disconnect()
手动断开 WebSocket 连接。
javascript
const {disconnect} = useSocket({url: 'ws://localhost:8080/ws'});
disconnect();
send(data)
发送消息到服务器。
参数:
data
(string | object): 要发送的数据,可以是字符串或对象
返回值: Promise
javascript
// 发送字符串
await send('Hello World');
// 发送对象
await send({
type: 'message',
content: 'Hello World',
timestamp: Date.now(),
});
clearMessages()
清空消息列表。
javascript
const {clearMessages} = useSocket({url: 'ws://localhost:8080/ws'});
clearMessages();
实际项目使用示例
聊天应用示例
javascript
<template>
<view class="chat-page">
<!-- 聊天消息列表 -->
<scroll-view class="message-list" scroll-y :scroll-top="scrollTop">
<view v-for="message in messages" :key="message.id" class="message-item">
<view class="message-avatar">
<image :src="message.avatar" mode="aspectFill" />
</view>
<view class="message-content">
<text>{{ message.content }}</text>
<text class="message-time">{{ message.timestamp }}</text>
</view>
</view>
</scroll-view>
<!-- 输入区域 -->
<view class="input-area">
<input
v-model="inputText"
placeholder="输入消息..."
@confirm="handleSend"
:disabled="!isConnected"
/>
<button
@click="handleSend"
:disabled="!isConnected || !inputText.trim()"
>
发送
</button>
</view>
</view>
</template>
<script setup>
import { ref, onMounted, nextTick } from 'vue';
import { useSocket } from '@/hooks/useSocket';
const inputText = ref('');
const scrollTop = ref(0);
// 创建 WebSocket 连接
const { isConnected, messages, send } = useSocket({
url: 'ws://localhost:8080/chat',
onMessage: (message) => {
console.log('收到聊天消息:', message);
// 自动滚动到底部
nextTick(() => {
scrollToBottom();
});
},
onOpen: () => {
console.log('聊天连接已建立');
},
onError: (error) => {
console.error('聊天连接错误:', error);
uni.showToast({
title: '连接失败',
icon: 'error'
});
}
});
// 发送消息
const handleSend = async () => {
if (!inputText.value.trim() || !isConnected.value) return;
const message = {
type: 'text',
content: inputText.value.trim(),
timestamp: new Date().toLocaleTimeString(),
id: Date.now()
};
try {
await send(message);
inputText.value = '';
} catch (error) {
console.error('发送消息失败:', error);
uni.showToast({
title: '发送失败',
icon: 'error'
});
}
};
// 滚动到底部
const scrollToBottom = () => {
const query = uni.createSelectorQuery();
query.select('.message-list').boundingClientRect();
query.exec((res) => {
if (res[0]) {
scrollTop.value = res[0].height;
}
});
};
onMounted(() => {
// 页面加载时自动连接
console.log('聊天页面已加载');
});
</script>
实时通知示例
javascript
<template>
<view class="notification-page">
<view class="status-indicator" :class="{ connected: isConnected }">
<text>{{ isConnected ? '在线' : '离线' }}</text>
</view>
<view class="notification-list">
<view
v-for="notification in messages"
:key="notification.id"
class="notification-item"
>
<text>{{ notification.content }}</text>
<text class="notification-time">{{ notification.timestamp }}</text>
</view>
</view>
</view>
</template>
<script setup>
import { useSocket } from '@/hooks/useSocket';
// 创建通知 WebSocket 连接
const { isConnected, messages } = useSocket({
url: 'ws://localhost:8080/notifications',
onMessage: (notification) => {
console.log('收到通知:', notification);
// 显示系统通知
uni.showToast({
title: notification.content,
icon: 'none',
duration: 3000
});
},
onOpen: () => {
console.log('通知连接已建立');
}
});
</script>
注意事项
1. 网络环境
- 确保 WebSocket 服务器地址正确且可访问
- 在真机调试时,需要使用实际的 IP 地址而非 localhost
2. 生命周期管理
- Hook 会在组件卸载时自动断开连接
- 如需在多个组件间共享连接,建议使用全局状态管理
3. 错误处理
- 建议为所有回调函数提供错误处理逻辑
- 网络异常时会有自动重连机制,但达到最大重连次数后需要手动处理
4. 性能优化
- 消息列表会持续增长,建议在适当时机调用
clearMessages()
清空 - 心跳间隔不宜过短,避免频繁的网络请求
5. 平台兼容性
- 本 Hook 基于 UniApp 的
uni.connectSocket
API - 支持所有 UniApp 支持的平台(H5、小程序、App)
完整源码
useSocket.js 源码
javascript
// composables/useWebSocket.js
import {ref, reactive, onUnmounted} from 'vue';
export function useSocket(options = {}) {
// 状态
const isConnected = ref(false);
const reconnectCount = ref(0);
const messages = ref([]);
const socketTask = ref(null);
// 配置
const config = reactive({
url: options.url || '',
maxReconnectCount: options.maxReconnectCount || 5, // 最大重连次数
reconnectInterval: options.reconnectInterval || 3000, // 重连间隔
autoConnect: options.autoConnect !== false, // 自动连接
heartBeatInterval: options.heartBeatInterval || 5000, // 心跳间隔
...options,
});
// 心跳计时器
let heartBeatTimer = null;
// 连接 WebSocket
const connect = () => {
if (socketTask.value) {
disconnect();
}
// console.log('正在连接 WebSocket...');
socketTask.value = uni.connectSocket({
url: config.url,
header: {
'content-type': 'application/json',
},
success: () => {
console.log('[WebSocket] 连接创建成功');
},
fail: (err) => {
console.error('[WebSocket] 连接创建失败', err);
options.onError?.(err);
handleReconnect();
},
});
bindEvents();
};
// 绑定事件
const bindEvents = () => {
if (!socketTask.value) return;
socketTask.value.onOpen(() => {
console.log('[WebSocket] 连接已打开');
isConnected.value = true;
reconnectCount.value = 0;
options.onOpen?.();
startHeartBeat();
});
socketTask.value.onMessage((res) => {
const message = parseMessage(res.data);
options.onMessage?.(message);
messages.value.push(message);
});
socketTask.value.onError((err) => {
console.error('[WebSocket] 连接错误:', err);
isConnected.value = false;
options.onError?.(err);
handleReconnect();
});
socketTask.value.onClose((res) => {
console.log('[WebSocket] 连接已关闭', res);
isConnected.value = false;
options.onClose?.(res);
stopHeartBeat();
});
};
// 发送消息
const send = (data) => {
if (!isConnected.value || !socketTask.value) {
throw new Error('WebSocket 未连接');
}
const message = typeof data === 'string' ? data : JSON.stringify(data);
return new Promise((resolve, reject) => {
socketTask.value.send({
data: message,
success: () => {
console.log('消息发送成功');
resolve();
},
fail: (err) => {
console.error('消息发送失败', err);
reject(err);
},
});
});
};
// 断开连接
const disconnect = () => {
if (socketTask.value && socketTask.value.readyState === 1) {
socketTask.value.close();
socketTask.value = null;
}
isConnected.value = false;
stopHeartBeat();
};
// 重连机制
const handleReconnect = () => {
if (reconnectCount.value >= config.maxReconnectCount) {
console.log('[WebSocket] 已达到最大重连次数');
options.onMaxReconnect?.();
return;
}
reconnectCount.value++;
console.log(`[WebSocket] 尝试第 ${reconnectCount.value} 次重连...`);
setTimeout(() => {
connect();
}, config.reconnectInterval);
};
// 解析消息
const parseMessage = (data) => {
try {
return JSON.parse(data);
} catch {
return {
type: 'text',
content: data,
timestamp: Date.now(),
};
}
};
// 心跳检测
const startHeartBeat = () => {
if (!config.heartBeatInterval) return;
stopHeartBeat();
heartBeatTimer = setInterval(() => {
if (isConnected.value) {
send({
type: 'ping',
timestamp: Date.now(),
}).catch((err) => {
console.error('[WebSocket] 心跳发送失败', err);
});
}
}, config.heartBeatInterval);
};
const stopHeartBeat = () => {
if (heartBeatTimer) {
clearInterval(heartBeatTimer);
heartBeatTimer = null;
}
};
// 清空消息
const clearMessages = () => {
messages.value = [];
};
// 自动连接
if (config.autoConnect && config.url) {
connect();
}
// 组件卸载时清理
onUnmounted(() => {
disconnect();
});
return {
// 状态
isConnected,
reconnectCount,
messages,
// 方法
connect,
disconnect,
send,
clearMessages,
};
}
总结
useSocket
Hook 为 UniApp 项目提供了完整的 WebSocket 解决方案,具有以下优势:
- 开箱即用 - 简单的 API 设计,快速上手
- 功能完整 - 包含连接管理、重连、心跳等完整功能
- 错误处理 - 完善的错误处理和回调机制
- 性能优化 - 自动清理和生命周期管理
- 易于扩展 - 基于 Vue 3 Composition API,易于扩展和定制
通过本说明文档,您应该能够快速在项目中使用 useSocket
实现 WebSocket 功能。如有任何问题,请参考示例代码或查看源码实现。