第六章:高级功能(WebSocket、SSE)
6.1 WebSocket 支持
HTMX 原生支持 WebSocket,可以实现双向实时通信。
基础用法
<!-- 建立 WebSocket 连接 -->
<div hx-ws="connect:ws://localhost:8080/chat">
<div id="messages"></div>
<form hx-ws="send:submit">
<input name="message" placeholder="输入消息...">
<button type="submit">发送</button>
</form>
</div>
完整聊天室示例
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/htmx.org@1.9.12"></script>
<style>
#chat-box {
height: 300px;
overflow-y: auto;
border: 1px solid #ccc;
padding: 10px;
margin-bottom: 10px;
}
.message {
padding: 5px;
margin: 5px 0;
background: #f0f0f0;
border-radius: 5px;
}
.message.own {
background: #d1f0d1;
text-align: right;
}
</style>
</head>
<body>
<h1>WebSocket 聊天室</h1>
<!-- WebSocket 连接容器 -->
<div hx-ws="connect:wss://example.com/chat">
<div id="chat-box" hx-swap="beforeend" scroll:bottom>
<!-- 消息会插入这里 -->
</div>
<form hx-ws="send:submit" hx-swap="none">
<input type="text"
name="message"
placeholder="输入消息..."
required
style="width: 300px;"
003e
<button type="submit">发送</button>
</form>
</div>
</body>
</html>
后端(Node.js + ws):
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
wss.on('connection', (ws) => {
ws.on('message', (message) => {
const data = JSON.parse(message);
// 广播给所有客户端
const html = `<div class="message">${data.message}</div>`;
wss.clients.forEach(client => {
if (client.readyState === WebSocket.OPEN) {
client.send(html);
}
});
});
});
6.2 Server-Sent Events (SSE)
SSE 适合服务器向客户端推送单向数据流。
基础用法
<!-- 建立 SSE 连接 -->
<div hx-sse="connect:/events">
<div hx-sse="swap:message">
等待消息...
</div>
</div>
实时通知示例
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/htmx.org@1.9.12"></script>
<style>
#notifications {
position: fixed;
top: 20px;
right: 20px;
width: 300px;
}
.notification {
background: #4CAF50;
color: white;
padding: 15px;
margin: 5px 0;
border-radius: 5px;
animation: slideIn 0.3s ease-out;
}
@keyframes slideIn {
from { transform: translateX(100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
</style>
</head>
<body>
<h1>实时通知系统</h1>
<div id="notifications"
hx-sse="connect:/sse/notifications"
hx-sse="swap:beforeend"
003e
</div>
</body>
</html>
后端(Node.js):
app.get('/sse/notifications', (req, res) => {
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
const sendNotification = () => {
const html = `
<div class="notification">
新消息:${new Date().toLocaleTimeString()}
</div>
`;
res.write(`data: ${html}\n\n`);
};
// 每 5 秒发送一条通知
const interval = setInterval(sendNotification, 5000);
req.on('close', () => {
clearInterval(interval);
});
});
6.3 SSE 高级用法
命名事件
<!-- 处理不同类型的 SSE 事件 -->
<div hx-sse="connect:/sse/updates">
<!-- 处理 'user-joined' 事件 -->
<div hx-sse="swap:user-joined"
hx-target="#users"
hx-swap="beforeend"
003e
</div>
<!-- 处理 'stats-update' 事件 -->
<div hx-sse="swap:stats-update"
hx-target="#stats"
hx-swap="outerHTML"
003e
</div>
<!-- 处理默认消息事件 -->
<div hx-sse="swap:message"
hx-target="#messages"
003e
</div>
</div>
后端发送命名事件:
// 发送命名事件
res.write(`event: user-joined\n`);
res.write(`data: <div>新用户加入!</div>\n\n`);
res.write(`event: stats-update\n`);
res.write(`data: <span id="stats">在线: 100</span>\n\n`);
// 默认事件(无 event: 行)
res.write(`data: <div>普通消息</div>\n\n`);
6.4 实时数据流示例
股票价格推送
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/htmx.org@1.9.12"></script>
<style>
.stock-price { font-size: 24px; font-weight: bold; }
.up { color: green; }
.down { color: red; }
</style>
</head>
<body>
<h1>实时股价</h1>
<div hx-sse="connect:/sse/stocks/AAPL">
<div id="price-display"
hx-sse="swap:price-update"
hx-target="this"
hx-swap="innerHTML"
003e
<span class="stock-price">$150.00</span>
</div>
</div>
</body>
</html>
后端:
app.get('/sse/stocks/:symbol', (req, res) => {
res.setHeader('Content-Type', 'text/event-stream');
const symbol = req.params.symbol;
let price = 150.00;
const updatePrice = () => {
// 模拟价格变动
const change = (Math.random() - 0.5) * 2;
price += change;
const cssClass = change >= 0 ? 'up' : 'down';
const html = `
<span class="stock-price ${cssClass}">
$${price.toFixed(2)}
</span>
`;
res.write(`event: price-update\n`);
res.write(`data: ${html}\n\n`);
};
const interval = setInterval(updatePrice, 1000);
req.on('close', () => clearInterval(interval));
});
6.5 WebSocket vs SSE 选择指南
| 特性 | WebSocket | SSE |
|---|
| 方向 | 双向 | 单向(服务器→客户端) |
| 协议 | ws:// / wss:// | HTTP |
| 重连 | 需手动实现 | 自动重连 |
| 浏览器支持 | 现代浏览器 | 除 IE 外全支持 |
| 使用场景 | 聊天、游戏、协作编辑 | 通知、实时数据、股票 |
| 复杂度 | 较高 | 简单 |
| 穿透代理 | 可能受阻 | 通常无障碍 |
6.6 重连与错误处理
<!-- 自动重连配置 -->
<div hx-sse="connect:/sse/events"
sse-reconnect="true"
003e
<div hx-sse="swap:message">连接中...</div>
</div>
<script>
// 监听连接事件
document.body.addEventListener('htmx:sseConnected', function(evt) {
console.log('SSE 连接成功');
});
document.body.addEventListener('htmx:sseError', function(evt) {
console.error('SSE 连接错误:', evt.detail.error);
});
document.body.addEventListener('htmx:sseClosed', function(evt) {
console.log('SSE 连接关闭');
});
// WebSocket 事件
document.body.addEventListener('htmx:wsConnecting', function(evt) {
console.log('WebSocket 连接中...');
});
document.body.addEventListener('htmx:wsOpen', function(evt) {
console.log('WebSocket 连接成功');
});
document.body.addEventListener('htmx:wsClose', function(evt) {
console.log('WebSocket 连接关闭');
});
</script>
6.7 扩展:使用扩展增强功能
SSE 扩展(新版 HTMX)
<script src="https://unpkg.com/htmx.org@1.9.12/dist/ext/sse.js"></script>
<div hx-ext="sse" sse-connect="/sse/events">
<div sse-swap="message">等待消息...</div>
</div>
WebSocket 扩展
<script src="https://unpkg.com/htmx.org@1.9.12/dist/ext/ws.js"></script>
<div hx-ext="ws" ws-connect="wss://example.com/chat">
<div id="messages"></div>
<form ws-send>
<input name="message">
<button>发送</button>
</form>
</div>
6.8 小结
- WebSocket:适合双向实时通信(聊天、协作)
- SSE:适合服务器推送(通知、数据流)
- HTMX 让实时功能实现变得异常简单
下一章预告:第七章将讲解与主流后端框架的集成。
第六章完