useRef — nó là gì, hook tham chiếu và làm việc với DOM trong React

Tác giả: IT Sectr Đã đăng: 2026-07-04 Thời gian đọc: 9 phút

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 — hook để tạo đối tượng ref có thể thay đổi với trường .current.
  • Không render lại — thay đổi .current không gây render lại component.
  • Tham chiếu DOM — được truyền vào prop ref của phần tử để truy cập trực tiếp nút DOM.
  • Lưu trữ giá trị — phù hợp cho bộ đếm thời gian, giá trị trước đó và bất kỳ dữ liệu nào giữa các lần render.
  • Tham chiếu ổn định — đối tượng ref không thay đổi giữa các lần render, không giống như closure.

useRef trong React là gì

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ý.

jsx
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!
}

Truy cập phần tử DOM qua useRef

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.

jsx
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.

Lưu trữ giá trị có thể thay đổi mà không render lại

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.

jsx
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></>;
}

useRef so với useState: khi nào dùng cái gì

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íuseRefuseState
Render lạiKhông gây ra khi thay đổiGây ra ở mỗi setState
Thay đổiTrực tiếp: ref.current = giá trịQua setter: setState(giá trị)
Sử dụng trong JSXKhô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.

useRef kết hợp với useEffect

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.

jsx
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 thường gặp với useRef

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.

  • Ref trong JSX — đọc ref.current bên trong hàm render dẫn đến mất đồng bộ giữa UI và dữ liệu.
  • Ref là phụ thuộc của useEffect — ref.current không nên có trong mảng phụ thuộc; React không theo dõi các thay đổi của nó.
  • Quên kiểm tra null — khi unmount, ref.current trở thành null; hãy kiểm tra trước khi truy cập.
  • Lưu trữ hàm trong ref — cập nhật ref.current ở mỗi lần render nếu bạn dùng mẫu callback mới.
jsx
// ❌ Đừ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ó thể dùng useRef để lưu trạng thái trước đó không?

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.

Tại sao ref.current là null ở lần render đầu tiên?

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.

Sự khác biệt giữa useRef và createRef là gì?

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ó thể truyền useRef qua props không?

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.

Cách đo kích thước phần tử bằng useRef?

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

  • useRef — hook tạo đối tượng ref có thể thay đổi, được giữ giữa các lần render mà không gây render lại.
  • Truy cập DOM — prop ref được truyền cho phần tử để truy cập trực tiếp nút DOM; khả dụng sau khi mount.
  • Lưu trữ dữ liệu — bộ đếm giờ, callback, cờ và mọi giá trị không ảnh hưởng UI đều lưu trong ref.
  • useRef so với useState — chọn useState nếu thay đổi cần render lại UI; useRef nếu không.
  • Mẫu giá trị trước đó — lưu giá trị trước đó trong ref, cập nhật trong useEffect để so sánh.
  • Ref trong JSX — không đọc ref.current trong giai đoạn render; điều này gây mất đồng bộ UI và dữ liệu.
  • forwardRef — dùng để truyền ref qua props đến component con.

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.

Thảo luận dự án

Đọc thêm