Xây dựng ứng dụng realtime (thời gian thực) trong React/Next.js đòi hỏi việc lựa chọn đúng giao thức kết nối và chiến lược render thông minh, đặc biệt với các dữ liệu nhảy liên tục như bảng giá chứng khoán/crypto.
1. Lựa chọn Giao thức (Protocol Selection)
Không phải cứ realtime là dùng WebSocket. Hãy chọn công cụ phù hợp với bài toán.
| Tiêu chí | Short/Long Polling | Server-Sent Events (SSE) | WebSocket (WS) |
|---|---|---|---|
| Cơ chế | Client hỏi liên tục | Server đẩy 1 chiều | Hai chiều (Full-duplex) |
| Độ phức tạp | Thấp (Dùng fetch/SWR) | Trung bình (cần setup Backend) | Cao (Quản lý kết nối, re-connect) |
| Tài nguyên | Tốn kém (HTTP Overhead) | Nhẹ, tiết kiệm pin cho mobile | Giữ kết nối liên tục |
| Use Case | Dashborad ít thay đổi, Email | Thông báo, News Feed, ChatGPT Stream | Chat, Game, Trading, Whiteboard |
💡 Lời khuyên
- Nếu chỉ cần nhận thông báo tin tức hoặc tiến độ xử lý: Dùng SSE (đơn giản, tự động reconnect).
- Nếu cần chat 2 chiều hoặc Trading (delay < 50ms): Dùng WebSocket.
- Nếu dữ liệu thay đổi chậm (mỗi 30s): Dùng SWR/React Query Polling cho nhàn.
2. Bài toán: Bảng giá Chứng khoán (High-Frequency Updates)
Giả sử bạn nhận được 50-100 msg/giây qua WebSocket. Nếu mỗi lần nhận message bạn lại setState, React sẽ render lại 50-100 lần/giây ➡️ App Crashing 💥.
Dưới đây là các kỹ thuật tối ưu từ cơ bản đến nâng cao.
Level 1: Throttling (Bóp băng thông render)
Không render mọi message. Lưu message vào một hàng đợi (buffer) và chỉ update state sau mỗi khoảng thời gian cố định (ví dụ 1000ms).
// ✅ Dùng useRef để gom data, setInterval để render
const useThrottledData = (socketData) => {
const dataRef = useRef(socketData);
const [renderData, setRenderData] = useState(socketData);
useEffect(() => {
dataRef.current = socketData; // Luôn giữ data mới nhất trong ref (không gây render)
}, [socketData]);
useEffect(() => {
const interval = setInterval(() => {
// Chỉ update giao diện 1 lần mỗi giây
setRenderData(dataRef.current);
}, 1000);
return () => clearInterval(interval);
}, []);
return renderData;
};Level 2: Batching Updates (Gom nhóm)
Thay vì update từng dòng, hãy gom 50 updates thành 1 mảng và update một thể. React 18 đã có Automatic Batching, nhưng với WebSocket event, bạn đôi khi cần tự quản lý buffer.
Level 3: Bỏ qua React State (Direct DOM Manipulation) 🚀
Khi setState vẫn quá chậm (do Virtual DOM diffing), hãy dùng ref để chọc thẳng vào DOM.
Tại sao? Vì React cần tính toán cây Diff. Nếu update text của 1 cell trong bảng 1000 dòng, React vẫn phải check cả bảng.
const StockPrice = ({ symbol }) => {
const priceRef = useRef<HTMLSpanElement>(null);
const prevPrice = useRef(0);
// Giả sử có 1 custom hook `useSocketSubscription`
// trả về callback thay vì state
useSocketSubscription(symbol, (newPrice) => {
if (priceRef.current) {
// 1. Update text trực tiếp (Zero React Render)
priceRef.current.innerText = formatMoney(newPrice);
// 2. Thêm class màu xanh/đỏ xử lý animation bằng Native DOM
const colorClass = newPrice > prevPrice.current ? 'text-green-500' : 'text-red-500';
priceRef.current.className = `price-cell ${colorClass}`;
prevPrice.current = newPrice;
}
});
return <span ref={priceRef}>--</span>;
};👉 Đánh đổi: Code khó bảo trì hơn, mất tính declarative của React. Chỉ dùng cho các thành phần cực nóng (hot path).
Level 4: Virtualization (Cửa sổ hiển thị)
Nếu bảng giá có 5000 mã, nhưng user chỉ nhìn thấy 20 mã. Hãy dùng Virtual Scrolling (react-window hoặc tanstack-virtual).
Browser chỉ tốn sức vẽ 20 dòng. Việc update 4980 dòng còn lại trong memory rất nhanh.
Level 5: Canvas (Giao diện Games/Charts)
Nếu DOM vẫn chậm (ví dụ biểu đồ nến nhảy từng mili-giây), hãy vẽ lên thẻ <canvas>.
Lúc này bạn hoàn toàn thoát khỏi DOM. Hiệu năng đạt mức 60FPS mượt mà.
3. Web Workers (Xử lý Data nặng)
Nếu việc parse JSON từ WebSocket hoặc tính toán chỉ số (RSI, MACD) làm đơ UI thread. Hãy đẩy việc đó sang Web Worker.
- Main Thread: Chỉ nhận data đã xử lý -> Render UI.
- Worker Thread: Nhận WebSocket data -> JSON.parse() -> Tính toán -> Gửi về Main Thread.
4. Tóm tắt chiến lược
- Chậm & Thưa: Dùng SWR Polling.
- Thông báo/Stream: Dùng SSE.
- Chat/Trading: Dùng WebSocket.
- Tối ưu Trading UI:
- Throttling: React render tối đa 10-30 lần/giây.
- Ref/Native DOM: Dùng cho các con số nhảy liên tục.
- Virtualization: Cho các list dài.
- Worker: Cho tính toán nặng.