如何通过Node.js Express监听Firestore变化并向React实时推送数据
实现Firestore实时更新向React客户端推送的解决方案
核心问题
你当前使用的HTTP POST请求属于请求-响应模式,单次请求仅能返回一次响应,完全无法适配Firestore onSnapshot这种持续监听集合变化的场景。要实现服务器向客户端主动推送实时数据,必须改用支持长连接、主动推送的技术,推荐两种方案:Server-Sent Events(SSE)或WebSocket。
方案一:Server-Sent Events(SSE)
SSE是基于HTTP的单向推送技术,实现成本低,适合仅需服务器向客户端推送更新的场景。
后端(Express)代码改造
const express = require('express'); const admin = require('firebase-admin'); const app = express(); // 配置CORS(适配前端跨域请求) app.use((req, res, next) => { res.header('Access-Control-Allow-Origin', 'http://localhost:3000'); // 替换为你的前端地址 res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization'); res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); res.header('Access-Control-Allow-Credentials', 'true'); if (req.method === 'OPTIONS') return res.sendStatus(200); next(); }); // SSE接口:监听Orders集合并推送实时更新 app.get('/api/orders/sse', async (req, res) => { // 设置SSE专属响应头 res.setHeader('Content-Type', 'text/event-stream'); res.setHeader('Cache-Control', 'no-cache'); res.setHeader('Connection', 'keep-alive'); // 先发送初始全量数据 const ordersCol = admin.firestore().collection('Orders'); const initialSnapshot = await ordersCol.get(); const initialList = initialSnapshot.docs.map(doc => ({ id: doc.id, ...doc.data() })); res.write(`data: ${JSON.stringify({ list: initialList })}\n\n`); // 持续监听集合变化,推送更新数据 const unsubscribe = ordersCol.onSnapshot(snapshot => { const updatedList = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() })); // SSE消息格式要求:data: 开头,结尾必须加\n\n res.write(`data: ${JSON.stringify({ list: updatedList })}\n\n`); }, err => { console.error('Firestore监听错误:', err); res.end(); }); // 客户端断开连接时,清理Firestore监听 req.on('close', () => { unsubscribe(); res.end(); }); }); app.listen(3001, () => console.log('服务器运行于 http://localhost:3001'));
前端(React)代码改造
import { useEffect, useState } from 'react'; export default function OrdersList() { const [orderList, setOrderList] = useState([]); const token = '你的用户授权token'; // 替换为实际token获取逻辑 useEffect(() => { // 建立SSE连接 const eventSource = new EventSource('http://localhost:3001/api/orders/sse', { headers: { Authorization: `Bearer ${token}` } }); // 接收服务器推送的更新 eventSource.onmessage = (e) => { const res = JSON.parse(e.data); setOrderList(res.list); }; // 处理连接错误 eventSource.onerror = (err) => { console.error('SSE连接异常:', err); eventSource.close(); }; // 组件卸载时关闭SSE连接 return () => eventSource.close(); }, [token]); return ( <div className="orders-container"> {orderList.map(order => ( <div key={order.id} className="order-item"> <p>订单ID:{order.id}</p> <p>订单状态:{order.status}</p> </div> ))} </div> ); }
方案二:WebSocket(双向通信)
如果需要客户端向服务器发送指令(比如过滤条件),或者需要双向交互,推荐使用WebSocket。这里用ws库实现。
后端(Express)代码改造
const express = require('express'); const admin = require('firebase-admin'); const WebSocket = require('ws'); const http = require('http'); const app = express(); const server = http.createServer(app); const wss = new WebSocket.Server({ server }); // 配置CORS app.use((req, res, next) => { res.header('Access-Control-Allow-Origin', 'http://localhost:3000'); res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization'); res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); res.header('Access-Control-Allow-Credentials', 'true'); if (req.method === 'OPTIONS') return res.sendStatus(200); next(); }); // 处理WebSocket连接 wss.on('connection', (ws, req) => { // 验证客户端token const token = req.headers.authorization?.split(' ')[1]; if (!token) return ws.close(401, '未授权'); // 发送初始数据 const sendInitialData = async () => { const ordersCol = admin.firestore().collection('Orders'); const snapshot = await ordersCol.get(); const list = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() })); ws.send(JSON.stringify({ type: 'INIT', list })); }; sendInitialData(); // 监听Firestore变化并推送 const unsubscribe = admin.firestore().collection('Orders').onSnapshot(snapshot => { const updatedList = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() })); ws.send(JSON.stringify({ type: 'UPDATE', list: updatedList })); }, err => { console.error('Firestore监听错误:', err); ws.close(500, '服务器内部错误'); }); // 处理客户端发送的消息(双向交互示例) ws.on('message', (data) => { const msg = JSON.parse(data); if (msg.type === 'FILTER') { // 这里可以根据客户端发送的过滤条件,调整Firestore监听逻辑 console.log('收到过滤条件:', msg.condition); } }); // 客户端断开时清理资源 ws.on('close', () => unsubscribe()); }); server.listen(3001, () => console.log('服务器运行于 http://localhost:3001'));
前端(React)代码改造
import { useEffect, useState } from 'react'; export default function OrdersList() { const [orderList, setOrderList] = useState([]); const [wsConn, setWsConn] = useState(null); const token = '你的用户授权token'; useEffect(() => { // 建立WebSocket连接 const socket = new WebSocket('ws://localhost:3001', { headers: { Authorization: `Bearer ${token}` } }); socket.onopen = () => console.log('WebSocket连接成功'); // 接收服务器推送 socket.onmessage = (e) => { const res = JSON.parse(e.data); if (res.type === 'INIT' || res.type === 'UPDATE') { setOrderList(res.list); } }; socket.onerror = (err) => { console.error('WebSocket异常:', err); socket.close(); }; socket.onclose = () => console.log('WebSocket连接关闭'); setWsConn(socket); // 组件卸载时关闭连接 return () => socket.close(); }, [token]); // 向服务器发送过滤指令示例 const sendFilter = () => { if (wsConn && wsConn.readyState === WebSocket.OPEN) { wsConn.send(JSON.stringify({ type: 'FILTER', condition: { status: 'pending' } })); } }; return ( <div className="orders-container"> <button onClick={sendFilter}>只看待处理订单</button> {orderList.map(order => ( <div key={order.id} className="order-item"> <p>订单ID:{order.id}</p> <p>订单状态:{order.status}</p> </div> ))} </div> ); }
关键注意事项
- CORS配置:必须确保后端允许前端域名跨域访问,否则会出现请求失败。
- 资源清理:客户端断开连接时,一定要取消Firestore的
onSnapshot监听,避免内存泄漏。 - 身份验证:无论是SSE还是WebSocket,都要在连接时验证用户token,防止未授权访问。
内容的提问来源于stack exchange,提问作者Cloud Music
相关产品推荐
相关产品推荐

