在移动互联网应用中,实时互动功能已成为刚需。本文将以“实时投票”为案例,演示如何在uni-app中集成WebSocket,实现跨端(iOS/Android/小程序/Web)的投票数据实时同步。全文从连接建立、消息协议、心跳机制到UI更新,提供可直接运行的代码片段与设计思路。
一、为什么选择uni-app + WebSocket
uni-app基于Vue.js,一套代码发布多端,降低开发成本。WebSocket实现全双工通信,相比轮询更省流量、延迟更低。两者结合特别适合直播投票、在线问卷、实时竞价等场景。本案例采用原生WebSocket API(uni-app已封装),无需额外依赖。
二、项目结构与准备工作
新建uni-app项目(推荐使用HBuilder X),在pages目录下创建vote文件夹,包含vote.vue页面。同时准备一个简单的Node.js WebSocket服务端(用于演示,生产环境可替换为云函数或Java/Go服务)。
// 服务端示例 (Node.js + ws)
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 3001 });
let votes = { option1: 0, option2: 0, option3: 0 };
wss.on('connection', (ws) => {
// 发送当前票数
ws.send(JSON.stringify({ type: 'init', data: votes }));
ws.on('message', (message) => {
const { type, option } = JSON.parse(message);
if (type === 'vote' && votes[option] !== undefined) {
votes[option]++;
// 广播给所有客户端
wss.clients.forEach(client => {
if (client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify({ type: 'update', data: votes }));
}
});
}
});
});
服务端维护一个votes对象,收到投票消息后更新并广播。客户端连接后立即获取初始数据。
三、uni-app页面核心实现
在vote.vue中,我们需要处理:WebSocket连接、心跳保活、数据渲染、投票交互。以下为完整代码(已去除冗余样式,仅保留逻辑与结构)。
<template>
<view class="vote-container">
<view class="vote-title">实时投票:你最喜欢哪个框架?</view>
<view class="options">
<view class="option-item" v-for="(item, index) in options" :key="index" @click="handleVote(item.key)">
<text>{{ item.label }}</text>
<text class="count">{{ item.count }} 票</text>
<view class="progress-bar">
<view class="progress-fill" :style="'width:' + (item.percent || 0) + '%'"></view>
</view>
</view>
</view>
<view class="status">{{ connectionStatus }}</view>
</view>
</template>
<script>
export default {
data() {
return {
options: [
{ key: 'option1', label: 'Vue', count: 0, percent: 0 },
{ key: 'option2', label: 'React', count: 0, percent: 0 },
{ key: 'option3', label: 'Angular', count: 0, percent: 0 }
],
socketTask: null,
heartbeatTimer: null,
connectionStatus: '正在连接...'
}
},
mounted() {
this.connectWebSocket();
},
beforeDestroy() {
this.closeSocket();
},
methods: {
connectWebSocket() {
// uni-app 的 WebSocket 连接
this.socketTask = uni.connectSocket({
url: 'ws://localhost:3001', // 替换为实际服务器地址
success: () => {
console.log('连接发起成功');
},
fail: (err) => {
console.error('连接失败', err);
this.connectionStatus = '连接失败,请检查网络';
}
});
// 监听打开
this.socketTask.onOpen(() => {
console.log('WebSocket 已打开');
this.connectionStatus = '已连接';
this.startHeartbeat();
});
// 监听消息
this.socketTask.onMessage((res) => {
const msg = JSON.parse(res.data);
if (msg.type === 'init' || msg.type === 'update') {
this.updateVotes(msg.data);
} else if (msg.type === 'pong') {
// 心跳回应,不做额外处理
}
});
// 监听错误
this.socketTask.onError((err) => {
console.error('WebSocket 错误', err);
this.connectionStatus = '连接异常';
});
// 监听关闭
this.socketTask.onClose(() => {
console.log('WebSocket 关闭');
this.connectionStatus = '已断开';
this.stopHeartbeat();
// 可在此处添加重连逻辑
setTimeout(() => {
this.connectWebSocket();
}, 3000);
});
},
// 更新票数并计算百分比
updateVotes(data) {
const total = data.option1 + data.option2 + data.option3;
this.options.forEach(opt => {
opt.count = data[opt.key] || 0;
opt.percent = total > 0 ? ((opt.count / total) * 100).toFixed(1) : 0;
});
},
// 发起投票
handleVote(key) {
if (this.socketTask && this.connectionStatus === '已连接') {
this.socketTask.send({
data: JSON.stringify({ type: 'vote', option: key }),
success() {
console.log('投票消息发送成功');
}
});
} else {
uni.showToast({ title: '连接未就绪', icon: 'none' });
}
},
// 心跳机制:每15秒发送ping
startHeartbeat() {
this.heartbeatTimer = setInterval(() => {
if (this.socketTask) {
this.socketTask.send({
data: JSON.stringify({ type: 'ping' }),
fail() {
console.error('心跳发送失败');
}
});
}
}, 15000);
},
stopHeartbeat() {
if (this.heartbeatTimer) {
clearInterval(this.heartbeatTimer);
this.heartbeatTimer = null;
}
},
closeSocket() {
this.stopHeartbeat();
if (this.socketTask) {
this.socketTask.close();
this.socketTask = null;
}
}
}
}
</script>
上述代码实现了完整的WebSocket生命周期管理。注意:uni-app的WebSocket API与浏览器略有不同,需使用uni.connectSocket,并通过onMessage、onOpen等回调处理。
四、核心机制解析
1. 连接与重连
在mounted中建立连接,并在onClose中设置3秒后自动重连,保证网络波动后恢复。生产环境建议使用指数退避策略。
2. 心跳维持
每隔15秒发送{type:’ping’},服务端需回应{type:’pong’}。如果服务端没有回应,客户端可以在连续几次未收到pong后主动断开并重连。本案例简化了心跳响应,实际开发应增加超时检测。
3. 数据同步与广播
服务端收到投票后更新votes并广播update消息。客户端收到后直接替换本地数据,并重新计算百分比。这种设计保证所有客户端看到一致的结果。
4. 进度条动态展示
每个选项的.percent绑定内联宽度,实时反映投票比例。由于不使用style标签,我们直接在元素上使用:style绑定,这也是uni-app支持的动态样式写法。
五、多端适配与注意事项
uni-app的WebSocket在微信小程序中需要设置合法域名(在微信公众平台配置socket合法域名)。在App端,Android和iOS均支持原生WebSocket,无需额外配置。H5端则使用浏览器原生WebSocket。本案例代码在H5和小程序端测试通过。
另外,当应用进入后台时(尤其是移动端),WebSocket可能被系统挂起。建议在onshow/onhide中重新连接或暂停心跳,避免无效消耗。
六、完整服务端示例(补充)
为了让读者快速运行,这里给出更健壮的服务端代码(包含心跳响应)。
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 3001 });
let votes = { option1: 0, option2: 0, option3: 0 };
wss.on('connection', (ws) => {
ws.isAlive = true;
ws.send(JSON.stringify({ type: 'init', data: votes }));
ws.on('pong', () => {
ws.isAlive = true;
});
ws.on('message', (message) => {
const msg = JSON.parse(message);
if (msg.type === 'ping') {
ws.send(JSON.stringify({ type: 'pong' }));
} else if (msg.type === 'vote' && votes[msg.option] !== undefined) {
votes[msg.option]++;
wss.clients.forEach(client => {
if (client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify({ type: 'update', data: votes }));
}
});
}
});
ws.on('close', () => {
console.log('客户端断开');
});
});
// 心跳检测:每30秒检查一次,关闭未pong的客户端
const interval = setInterval(() => {
wss.clients.forEach((ws) => {
if (ws.isAlive === false) return ws.terminate();
ws.isAlive = false;
ws.ping();
});
}, 30000);
wss.on('close', () => clearInterval(interval));
服务端通过ping/pong机制检测客户端存活,并自动清理死连接。
七、总结与扩展
本文通过一个实时投票案例,完整展示了uni-app中WebSocket的使用方法,包括连接管理、心跳、数据广播与动态UI更新。读者可以在此基础上扩展为在线问卷、弹幕、协同编辑等更复杂的实时应用。
关键要点回顾:
- 使用uni.connectSocket建立连接,注意各端差异
- 心跳机制是长连接稳定的基础
- 数据同步采用全量替换,适合数据量小的场景;大数据量可考虑增量更新
- uni-app的:style绑定可实现动态进度条,无需额外样式标签
希望本文能帮助你快速搭建属于自己的实时交互应用。如果你有任何问题或改进建议,欢迎在评论区讨论(本页面为演示,不设评论区)。
本文为原创技术教程,基于uni-app 3.x版本,WebSocket协议标准。服务端使用Node.js ws库,客户端兼容H5、小程序、App端。

