useEffect vs useLayoutEffect: Khi Nào Dùng Cái Nào Cho Đúng?
Một trong những điểm dễ gây nhầm lẫn khi làm việc với React Hooks chính là sự tồn tại của hai hook có cú pháp gần như giống hệt nhau: useEffect và useLayoutEffect. Nếu không nắm vững nguyên lý hoạt động bên trong, bạn rất dễ chọn nhầm hook và vô tình gây ra hiện tượng giao diện bị giật (flickering) hoặc làm chậm toàn bộ ứng dụng. Bài viết này sẽ phân tích tường tận từng hook, giúp bạn luôn đưa ra quyết định chính xác trong mọi tình huống.

Trung Vũ Hoàng
Tác giả
1. Side Effects Trong React Là Gì?
Trước khi đi vào so sánh, hãy làm rõ khái niệm Side Effects. Đây là những tác vụ xảy ra bên ngoài luồng render của React, chẳng hạn như: gọi API lấy dữ liệu, thao tác trực tiếp lên DOM, hay thiết lập subscription cho các sự kiện bên ngoài.
Cả useEffect lẫn useLayoutEffect đều phục vụ mục đích xử lý những tác vụ này — sự khác biệt cốt lõi nằm ở thời điểm chúng thực thi tương quan với chu kỳ vẽ (paint) của trình duyệt.
2. Cơ Chế Hoạt Động: Bất Đồng Bộ vs Đồng Bộ
useEffect — Hook "Thân Thiện Với Hiệu Năng"
useEffect là lựa chọn mặc định và phù hợp cho đại đa số trường hợp. Chuỗi thực thi diễn ra như sau:
React nhận thấy State thay đổi.
React tiến hành render lại Component.
Trình duyệt vẽ (paint) giao diện mới ra màn hình.
useEffect mới bắt đầu chạy.
Vì callback chỉ được gọi sau khi màn hình đã hoàn tất cập nhật, useEffect không hề cản trở trình duyệt — giúp giao diện phản hồi nhanh và mượt mà với người dùng.
useLayoutEffect — Hook "Ưu Tiên Độ Chính Xác Thị Giác"
Ngược lại, useLayoutEffect chen vào trước bước vẽ của trình duyệt:
React nhận thấy State thay đổi.
React render lại Component.
useLayoutEffect thực thi ngay lập tức (đồng bộ).
Trình duyệt mới được phép vẽ giao diện.
Điều này có nghĩa là mọi thay đổi bạn thực hiện bên trong useLayoutEffect đều được áp dụng trước khi người dùng kịp nhìn thấy bất kỳ kết quả render nào.
3. So Sánh Chi Tiết Các Đặc Điểm
Dưới đây là những tiêu chí quan trọng nhất để phân biệt hai hook này:
Thời điểm thực thi
useEffect: Chạy sau khi trình duyệt đã hiển thị giao diện (After Paint).
useLayoutEffect: Chạy trước khi trình duyệt hiển thị giao diện (Before Paint).
Kiểu thực thi
useEffect: Bất đồng bộ (Async) — không chặn luồng render.
useLayoutEffect: Đồng bộ (Sync) — chặn render cho đến khi hoàn thành.
Tác động lên hiệu năng
useEffect: Thân thiện với tốc độ tải trang, không gây gián đoạn.
useLayoutEffect: Dễ khiến trang bị "đứng" nếu xử lý tác vụ nặng bên trong.
Trường hợp sử dụng điển hình
useEffect: Gọi API, Analytics, Event Listener — bất kỳ thứ gì không cần thấy trước khi paint.
useLayoutEffect: Đo kích thước DOM, điều chỉnh vị trí phần tử — những thứ người dùng cần thấy đúng ngay từ khung hình đầu tiên.
4. Khi Nào Thực Sự Cần useLayoutEffect?
Dù React chính thức khuyến nghị ưu tiên useEffect, vẫn tồn tại những tình huống mà useLayoutEffect là giải pháp duy nhất để tránh trải nghiệm người dùng kém.
Tình Huống 1: Ngăn Giao Diện Bị Nháy (Flickering)
Giả sử bạn cần tính toán vị trí chính xác của một Tooltip dựa trên kích thước thực tế của nút bấm:
Với useEffect: Tooltip xuất hiện ở vị trí cũ → Trình duyệt vẽ → useEffect tính lại → Re-render → Tooltip "nhảy" sang chỗ mới. Người dùng thấy hiệu ứng giật rõ ràng.
Với useLayoutEffect: Vị trí được tính toán xong trước khi trình duyệt vẽ. Tooltip xuất hiện đúng chỗ ngay từ đầu, người dùng không nhận thấy bất kỳ sự giật nào.
Tình Huống 2: Đo Đạc Kích Thước DOM
Khi dùng getBoundingClientRect() hay các API đo lường DOM khác để xây dựng layout phức tạp, useLayoutEffect đảm bảo các giá trị thu được là chính xác — phản ánh trạng thái DOM thực tế — trước khi người dùng nhìn thấy kết quả cuối cùng.
5. Phân Tích Thứ Tự Thực Thi
Hãy xem đoạn code minh họa sau để thấy rõ thứ tự ưu tiên:
useLayoutEffect(() => {
console.log("1. useLayoutEffect chạy");
}, [count]);
useEffect(() => {
console.log("2. useEffect chạy");
}, [count]);
Mỗi khi count thay đổi, console luôn in 1 trước rồi mới đến 2 — bất kể thứ tự bạn khai báo trong component. Sự ưu tiên này là cố định và được React đảm bảo.
6. Nguyên Tắc Vàng Khi Chọn Hook
Đúc kết từ thực tế phát triển sản phẩm: Luôn bắt đầu với useEffect. Chỉ chuyển sang useLayoutEffect khi bạn quan sát thấy giao diện bị nháy hoặc sai lệch thị giác.
Lạm dụng useLayoutEffect sẽ vô hiệu hóa các cơ chế tối ưu render của React Concurrent Mode, khiến ứng dụng không thể tận dụng khả năng phân chia và ưu tiên hóa render theo từng khung hình.
Lưu ý với Next.js và SSR: Khi làm việc với Server-Side Rendering, tránh dùng
useLayoutEffectở cấp top-level component vì server không có DOM — điều này sẽ tạo ra cảnh báo trong quá trình build và có thể gây lỗi hydration.
7. Tổng Kết
Nắm vững sự khác biệt giữa useEffect vs useLayoutEffect là dấu hiệu của một lập trình viên React chín chắn. Quy tắc đơn giản để nhớ:
useEffect→ Ưu tiên hiệu năng, chạy sau paint.useLayoutEffect→ Ưu tiên chính xác thị giác, chạy trước paint.
Hiểu rõ điểm này không chỉ giúp bạn trả lời tốt câu hỏi phỏng vấn mà còn xây dựng được những ứng dụng React có chất lượng thực sự cao.
Câu hỏi thường gặp
Bài viết liên quan

Zustand Async: 5 Cách Xử Lý Bất Đồng Bộ Hiệu Quả Trong React
Mọi ứng dụng React thực tế đều phải giao tiếp với API bất đồng bộ. Nếu quản lý kém, ứng dụng dễ rơi vào các vấn đề như UI bị đơ, race condition, rò rỉ bộ nhớ, hay hiển thị dữ liệu cũ. Zustand giải quyết bài toán này với một cú pháp cực kỳ đơn giản — định nghĩa async actions trực tiếp trong store, không cần middleware phức tạp như Redux Thunk hay Saga.

Promise Nâng Cao: all, allSettled, race, any — Khi Nào Dùng Cái Nào?
Nếu bạn chỉ dùng async/await tuần tự, ứng dụng của bạn đang bỏ phí tiềm năng hiệu suất — mỗi request phải chờ request trước xong mới bắt đầu. Các phương thức tĩnh của Promise (all, allSettled, race, any) cho phép bạn điều phối nhiều tác vụ bất đồng bộ song song theo chiến lược phù hợp từng bài toán.

Zustand Async: 5 Cách Xử Lý Bất Đồng Bộ Hiệu Quả Trong React
Mọi ứng dụng React thực tế đều phải giao tiếp với API bất đồng bộ. Nếu quản lý kém, ứng dụng dễ rơi vào các vấn đề như UI bị đơ, race condition, rò rỉ bộ nhớ, hay hiển thị dữ liệu cũ. Zustand giải quyết bài toán này với một cú pháp cực kỳ đơn giản — định nghĩa async actions trực tiếp trong store, không cần middleware phức tạp như Redux Thunk hay Saga.