Nỗi lo Re-render và lời giải từ Signals
Càng làm dự án React lớn, bạn sẽ càng thấy “thấm” nỗi khổ của re-render. Vấn đề cốt lõi nằm ở cơ chế cập nhật của React. Khi bạn thay đổi useState ở component cha, toàn bộ cây component con thường bị kéo vào vòng xoáy render lại, dù chúng chẳng hề dùng đến dữ liệu đó.
Lấy ví dụ từ một dự án dashboard chứng khoán mình từng tham gia. Với hơn 500 mã cổ phiếu cập nhật giá liên tục mỗi giây, việc dùng useState kết hợp Context API khiến CPU trình duyệt luôn ở mức 80-90%. FPS rớt thảm hại xuống còn 15-20, gây hiện tượng giật lag thấy rõ. Sau khi mình đề xuất chuyển sang Signals, FPS ổn định ở mức 60 mượt mà. Đội ngũ 5 developer của mình cũng thở phào vì không còn phải viết useMemo hay useCallback chằng chịt để chặn render thừa nữa.
Signals không phải khái niệm mới vì nó đã quá nổi tiếng trong SolidJS hay Preact. Tuy nhiên, khi đưa vào React, nó thay đổi hoàn toàn cuộc chơi. Thay vì render lại cả component, Signals cập nhật trực tiếp giá trị tại đúng vị trí hiển thị trên DOM. Bạn hãy tưởng tượng mình chỉ cần thay đổi một con số trên màn hình mà không cần làm phiền đến cả bộ máy component tree đồ sộ.
Cài đặt Signals vào dự án
Để bắt đầu, chúng ta sẽ sử dụng thư viện @preact/signals-react. Đây là giải pháp ổn định nhất hiện nay để mang cơ chế Signal vào hệ sinh thái React. Bạn mở terminal và chạy lệnh:
npm install @preact/signals-react
Một lưu ý nhỏ cho anh em dùng Vite hoặc Next.js: Để đạt hiệu suất tối đa và tự động theo dõi thay đổi, bạn nên cấu hình thêm plugin Babel. Tuy nhiên, ở mức độ làm quen, cài đặt thư viện gốc là đã đủ để trải nghiệm sự khác biệt.
Triển khai thực tế trong code
1. Khởi tạo Signal
Thay vì useState, bạn dùng hàm signal(). Điểm đặc biệt là bạn có thể khai báo nó ở ngoài component để biến thành một global state cực kỳ gọn nhẹ.
import { signal } from "@preact/signals-react";
// Khai báo ngoài component, truy cập được từ mọi file
const count = signal(0);
function Counter() {
return (
<div>
<p>Số lần bấm: {count.value}</p>
<button onClick={() => count.value++}>Tăng ngay</button>
</div>
);
}
2. Tối ưu với Computed Signals
Khi một giá trị phụ thuộc vào Signal khác, hãy dùng computed. Nó hoạt động tương tự useMemo nhưng thông minh hơn: chỉ tính toán lại khi dữ liệu gốc thực sự thay đổi.
import { signal, computed } from "@preact/signals-react";
const cartItems = signal([{ id: 1, price: 150000 }, { id: 2, price: 200000 }]);
// Tự động tính tổng tiền mỗi khi giỏ hàng cập nhật
const totalPrice = computed(() =>
cartItems.value.reduce((acc, item) => acc + item.price, 0)
);
function ShoppingCart() {
return <h2>Tổng thanh toán: {totalPrice.value} VNĐ</h2>;
}
3. Quản lý Side Effects với effect
Hàm effect() sẽ thay thế cho useEffect. Bạn không cần khai báo mảng dependency [] vì nó tự biết bạn đang dùng Signal nào bên trong để tự động chạy lại khi cần.
import { signal, effect } from "@preact/signals-react";
const theme = signal("dark");
effect(() => {
console.log(`Giao diện đã đổi sang: ${theme.value}`);
document.body.className = theme.value;
});
Kiểm chứng hiệu suất thực tế
Làm sao để biết Signals thực sự hiệu quả? Bạn hãy mở React DevTools phần Profiler lên.
Với useState, mỗi lần state đổi, component sẽ bị “tô màu” báo hiệu re-render. Với Signals, khi bạn thay đổi count.value, bạn sẽ thấy component chứa nó đứng yên hoàn toàn. Signals đã can thiệp trực tiếp vào text node trong DOM, bỏ qua bước diffing Virtual DOM nặng nề của React.
Kinh nghiệm quản lý state quy mô lớn
- Cấu trúc file: Nên gom Signals vào thư mục
store/. Tách riêngauthSignal.js,cartSignal.jsđể dễ bảo trì. - Dùng đúng chỗ: Đừng bỏ hoàn toàn
useState. Với những form đơn giản hoặc state chỉ dùng nội bộ một component,useStatevẫn là lựa chọn tinh gọn. - Tránh vòng lặp: Luôn kiểm tra kỹ các
effectđể tránh tình trạng Signal A cập nhật Signal B rồi quay lại A, gây treo trình duyệt.
Việc trace lỗi render trong các ứng dụng lớn thường rất tốn thời gian. Signals giúp tách biệt logic dữ liệu khỏi vòng đời component, giúp code dễ đọc và debug hơn hẳn. Nếu dự án của bạn đang gặp vấn đề về hiệu năng, hãy thử đưa Signals vào một module nhỏ. Bạn sẽ thấy sự khác biệt ngay lập tức.

