useRef là một hook React tạo ra một đối tượng ref có thể thay đổi với thuộc tính .current, được giữ lại giữa các lần render của component. Không giống như useState, thay đổi .current không gây ra render lại, khiến useRef trở nên lý tưởng để lưu trữ tham chiếu DOM, bộ đếm thời gian và bất kỳ giá trị nào cần tồn tại qua các lần render mà không vẽ lại UI. Theo Tài liệu React (2025), đối tượng ref được tạo một lần trong suốt vòng đời của component và không thay đổi giữa các lần render, đảm bảo tính ổn định của tham chiếu.
Những điểm chính
useRef là một hook được thêm vào React 16.8 trả về một đối tượng ref có thể thay đổi với một thuộc tính .current duy nhất. Giá trị ban đầu được gán cho .current khi component được mount. Sự khác biệt chính so với biến thông thường là đối tượng ref sống sót qua các lần render lại: trong các lần render tiếp theo, React không tạo lại ref mà trả về cùng một đối tượng.
Bên trong, useRef sử dụng cùng cơ chế với useState và useEffect — một danh sách liên kết các hook trên nút fiber. Tuy nhiên, không giống như useState, React không theo dõi các thay đổi của đối tượng ref, không xếp hàng đợi cập nhật và không lên lịch render lại. Điều này làm cho useRef trở thành một hook cực kỳ nhẹ, không ảnh hưởng đến hiệu suất ngay cả khi thay đổi thường xuyên.
Theo Nhóm React — Hooks FAQ (2025), useRef về cơ bản là một “chiếc hộp” lưu trữ giá trị. Đường cú pháp: const ref = useRef(giá_trị_ban_đầu) tương đương với const ref = { current: giá_trị_ban_đầu } với điểm khác biệt là React đảm bảo rằng đối tượng ref sẽ giống nhau qua mỗi lần render. Không có phép màu — chỉ là một tham chiếu ổn định được React quản lý.
import { useRef } from 'react';
function Component() {
const countRef = useRef(0);
// countRef.current = 0 ban đầu
// countRef.current = 5 sau khi thay đổi
// Không có render lại nào xảy ra!
}
Trường hợp sử dụng phổ biến nhất của useRef là truy cập trực tiếp vào một phần tử DOM. React gán cho elementRef.current một tham chiếu đến nút DOM sau khi component được mount. Điều này cần thiết để focus input, đo kích thước phần tử, tích hợp với thư viện hoạt ảnh và các thư viện không sử dụng React rendering.
React tự động quản lý các tham chiếu ref trên DOM: khi mount gán nút DOM thực tế, khi unmount đặt lại thành null. Điều này đảm bảo .current luôn chứa giá trị cập nhật và ngăn rò rỉ bộ nhớ qua các tham chiếu treo đến các phần tử đã bị xóa. Không cần dọn dẹp thủ công.
function AutoFocusInput() {
const inputRef = useRef(null);
useEffect(() => {
// Focus input sau khi component được mount
inputRef.current?.focus();
}, []);
return <input ref={inputRef} type="text" />;
}
Theo Tài liệu React — Refs và DOM (2025), không sử dụng ref cho các thao tác khai báo — mở/đóng modal, quản lý hiển thị. Cho những mục đích này, có state và props. Refs dành cho các thao tác mệnh lệnh không thể diễn đạt theo cách khai báo: focus, chọn văn bản, tích hợp với thư viện bên thứ ba.
Trường hợp sử dụng quan trọng thứ hai là lưu trữ giá trị tùy ý cần tồn tại giữa các lần render nhưng việc thay đổi chúng không được gây ra render lại. Ví dụ điển hình: định danh bộ đếm thời gian (setInterval/setTimeout), cờ hủy yêu cầu, giá trị prop trước đó để so sánh và bất kỳ dữ liệu nào không ảnh hưởng đến đầu ra UI.
Điều này đặc biệt hữu ích trong useEffect: useRef lưu trữ định danh bộ đếm thời gian và cleanup xóa nó. Nếu timerId được lưu trong useState, mỗi lần gọi setTimerId sẽ gây ra render lại không cần thiết, không cần cho cả logic lẫn UI. useRef giải quyết vấn đề này mà không có chi phí và không render lại không cần thiết.
function Timer() {
const intervalRef = useRef(null);
const start = () => {
intervalRef.current = setInterval(() => {
console.log('tích tắc');
}, 1000);
};
const stop = () => {
clearInterval(intervalRef.current);
};
useEffect(() => stop, []);
return <><button onClick={start}>Start</button><button onClick={stop}>Stop</button></>;
}
Thế lưỡng nan chính — chọn giữa useRef và useState — được giải quyết bằng một câu hỏi: “Có cần render lại khi giá trị thay đổi không?”. Nếu có — useState. Nếu không — useRef. useState lưu trữ trạng thái ảnh hưởng đến đầu ra của component; useRef lưu trữ dữ liệu cần cho logic nội bộ nhưng không ảnh hưởng đến UI.
Trong thực tế, các nhà phát triển thường sử dụng useRef để lưu trữ callback nhằm tránh vấn đề với closure. Ví dụ, nếu useEffect đăng ký một sự kiện và callback cần state hiện tại — hãy lưu callback trong useRef. Ở mỗi lần render, cập nhật ref.current với một hàm mới, và effect sẽ luôn gọi callback mới mà không cần đăng ký lại.
| Tiêu chí | useRef | useState |
|---|---|---|
| Render lại | Không gây ra khi thay đổi | Gây ra ở mỗi setState |
| Thay đổi | Trực tiếp: ref.current = giá trị | Qua setter: setState(giá trị) |
| Sử dụng trong JSX | Không dùng (không hiệu quả) | Dùng trong đầu ra component |
| Ví dụ | Bộ đếm giờ, tham chiếu DOM, giá trị trước đó | Dữ liệu form, trạng thái UI, cờ |
Có một phản mẫu: sử dụng useRef cho dữ liệu cần trong JSX nhưng việc thay đổi không được gây render lại. Điều này dẫn đến mất đồng bộ — UI hiển thị dữ liệu cũ trong khi ref.current đã mới. Nếu giá trị được hiển thị trong UI — hãy dùng useState. Nếu chỉ dùng nội bộ — useRef.
Sự kết hợp useRef + useEffect là mẫu tiêu chuẩn để theo dõi các giá trị prop trước đó. Lưu giá trị trước đó trong ref, so sánh với giá trị hiện tại trong useEffect và đưa ra quyết định dựa trên sự khác biệt. Điều này đặc biệt hữu ích trong hoạt ảnh khi bạn cần biết giá trị trước khi thay đổi.
function PriceDisplay({ price }) {
const prevPriceRef = useRef(price);
useEffect(() => {
const prevPrice = prevPriceRef.current;
if (price > prevPrice) {
animateUp();
} else if (price < prevPrice) {
animateDown();
}
prevPriceRef.current = price;
}, [price]);
return <span>${price}</span>;
}
Theo Tài liệu React — Hooks FAQ (2025), mẫu này được gọi là “mẫu giá trị trước đó”. Nó hoạt động vì useRef giữ giá trị giữa các lần render và useEffect chạy sau khi các thay đổi được xác nhận. Đầu tiên DOM được cập nhật với price mới, sau đó useEffect so sánh nó với giá trị trước đó (vẫn còn trong ref) và cập nhật ref.current thành giá trị hiện tại.
Lỗi phổ biến nhất là đọc ref.current trong giai đoạn render để tính toán JSX. Vì thay đổi ref.current không gây render lại, component có thể sử dụng giá trị cũ. Nếu .current tham gia vào đầu ra UI — hãy dùng useState. Nếu cần đồng bộ ref và state, hãy dùng useEffect để cập nhật state từ ref.
// ❌ Đừng đọc ref.current trong lúc render để hiển thị
function BadComponent() {
const valRef = useRef(0);
return <p>{valRef.current}</p>; // SẼ KHÔNG cập nhật khi thay đổi
}
// ✅ Dùng state để hiển thị, ref cho logic
function GoodComponent() {
const [val, setVal] = useState(0);
const valRef = useRef(0);
return <p>{val}</p>;
}
Một lỗi phổ biến khác là sử dụng useRef như cách duy nhất để lưu trạng thái trong component khi dữ liệu thực sự ảnh hưởng đến UI. Các nhà phát triển chọn useRef để tránh render lại “không cần thiết”, nhưng kết quả là có một UI không cập nhật. Cách tiếp cận đúng: dùng useState cho dữ liệu UI và chỉ dùng useRef cho các giá trị phụ trợ không tham gia vào việc render.
Câu hỏi thường gặp
Có, đây là một mẫu phổ biến — mẫu trạng thái trước đó. Tạo một ref và cập nhật nó trong useEffect mỗi khi giá trị được theo dõi thay đổi. Giữa lần render và effect, ref.current chứa giá trị trước đó, có thể so sánh với giá trị hiện tại. Điều này không yêu cầu render lại thêm và hoạt động với mọi kiểu dữ liệu.
React gán ref.current giá trị nút DOM chỉ sau khi phần tử được render và thêm vào DOM thực tế. Trong lần render đầu tiên, component chưa được mount, vì vậy ref.current bằng giá trị ban đầu (null). Truy cập DOM qua ref.current chỉ khả dụng trong useEffect hoặc trong các trình xử lý sự kiện được gọi sau khi mount.
createRef tạo một đối tượng ref mới ở mỗi lần render — chỉ nên dùng trong class component. useRef tạo ref một lần và trả về cùng một đối tượng ở tất cả các lần render tiếp theo. Trong functional component, chỉ dùng useRef; createRef sẽ gây mất giá trị khi render lại vì đối tượng sẽ được tạo lại.
Có, đối tượng ref có thể được truyền như một prop thông thường: <Child inputRef={inputRef} />. Component con sử dụng nó qua inputRef.current. Tuy nhiên, để truyền ref trực tiếp vào phần tử DOM của component con, hãy dùng forwardRef — một HOC cho phép chuyển tiếp ref qua props. Nếu không có forwardRef, ref sẽ không tự động gắn vào phần tử DOM bên trong component con.
Gắn ref vào phần tử, sau đó trong useEffect đọc ref.current.getBoundingClientRect() hoặc ref.current.offsetWidth / offsetHeight. Để theo dõi thay đổi kích thước một cách phản ứng, hãy dùng ResizeObserver bên trong useEffect: tạo observer, đăng ký thay đổi và cập nhật state với kích thước mới. Đừng quên ngắt kết nối observer trong cleanup.
Tổng kết
Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.
Đọc thêm