0
0
Lập trình
Admin Team
Admin Teamtechmely

Hiểu React Hooks: Từ Cơ Bản đến Ngăn Ngừa Lỗi & Quyết Định Thiết Kế

Đăng vào 5 tháng trước

• 6 phút đọc

0. Giới thiệu

React Hooks là một cách để quản lý trạng thái và vòng đời mà không cần sử dụng lớp (class) (kể từ phiên bản 16.8).

  • Đơn giản hơn cú pháp lớp
  • Dễ dàng tái sử dụng logic (custom hooks)
  • Dễ kiểm tra hơn

👉 Mục tiêu của bài viết này:

  • Người mới bắt đầu: Sử dụng useState / useEffect một cách chính xác
  • Người trung cấp: Hiểu rõ về render/commit, stale closures, và các re-render do function props để đưa ra quyết định thiết kế hợp lý

1. Hình dung Render và Commit

  • Render: React gọi hàm component của bạn để tạo ra virtual DOM.
  • Commit: Sự khác biệt được áp dụng vào DOM thực.

👉 Hàm component được gọi bởi React - nội bộ như một phần của vòng đời, không phải bởi mã của bạn trực tiếp.


2. Các Hook Cơ Bản

2-1. useState

javascript Copy
const [user, setUser] = useState({ name: "Taro", age: 20 });

// ❌ setUser thay thế toàn bộ trạng thái
setUser({ age: 21 });
// => { age: 21 } (name bị mất)

// ✅ Spread để giữ lại phần còn lại
setUser(prev => ({ ...prev, age: 21 }));

useState — Các điểm chính

  • Giữ trạng thái bên trong một component
  • setState thay thế giá trị (không có phép hợp nhất một phần)
  • Cập nhật là bất đồng bộ; ưu tiên functional updater khi bạn cần giá trị mới nhất
  • Sử dụng cho dữ liệu cần phản ánh lên UI

2-2. useEffect

javascript Copy
useEffect(() => {
  const id = setInterval(() => console.log("tick"), 1000);
  return () => clearInterval(id);
}, []);

useEffect — Các điểm chính

  • Chạy các tác dụng phụ sau khi render
  • Kiểm soát thời điểm chạy với mảng phụ thuộc
  • Trả về một hàm dọn dẹp để giải phóng tài nguyên
  • Tuyệt vời cho các cuộc gọi async, đăng ký, thao tác DOM, v.v.

2-3. useContext

javascript Copy
const ThemeContext = createContext("light");
const theme = useContext(ThemeContext);

useContext — Các điểm chính

  • Chia sẻ giá trị với các thành phần con mà không cần prop drilling
  • Khi giá trị thay đổi, tất cả người tiêu dùng sẽ re-render
  • Hữu ích cho cài đặt toàn cầu (theme, thông tin xác thực, v.v.)
  • Đối với trạng thái quy mô lớn, xem xét Redux hoặc Zustand

3. Phụ thuộc hiệu ứng, Stale Closures, và Cách Giải Quyết Chúng với useRef

Stale closure là gì?

Các component hàm tạo ra một phạm vi hàm mới trên mỗi lần render.
Kết quả là, các callback đã giữ biến tại thời điểm render có thể tiếp tục đọc giá trị cũ - đó là một stale closure.


❌ Lỗi do stale closure

javascript Copy
function Timer() {
  const [count, setCount] = useState(0);

  function tick() {
    // ❌ 'count' có thể là stale ở đây
    setCount(count + 1);
  }

  useEffect(() => {
    const id = setInterval(tick, 1000);
    return () => clearInterval(id);
  }, []); // 'tick' không có trong deps
}

👉 tick tiếp tục tham chiếu đến count ban đầu, vì vậy nó không bao giờ tăng lên quá 0.


✅ Tránh nó với một cập nhật chức năng

javascript Copy
useEffect(() => {
  const id = setInterval(() => {
    setCount(c => c + 1); // ✅ luôn sử dụng giá trị mới nhất
  }, 1000);
  return () => clearInterval(id);
}, []);

✅ Tránh nó với useRef

javascript Copy
function Timer() {
  const [count, setCount] = useState(0);
  const countRef = useRef(0);

  useEffect(() => {
    const id = setInterval(() => {
      countRef.current += 1;
      setCount(countRef.current);
    }, 1000);
    return () => clearInterval(id);
  }, []);
}

useRef — Các điểm chính

  • Giữ một giá trị có thể thay đổi trong .current
  • Cập nhật nó không kích hoạt re-render
  • Cũng được sử dụng cho tham chiếu đến các phần tử DOM
  • Ngăn chặn stale closures bằng cách luôn đọc giá trị mới nhất
  • Tuyệt vời cho các giá trị không cần thiết trong UI hoặc các giá trị tạm thời

4. useCallback và Re-renders Do “Hàm Là Đối Tượng”

Điều gì xảy ra khi truyền một hàm cho một thành phần con?

  1. Cha re-render.
  2. Định nghĩa hàm của cha chạy lại, tạo ra một đối tượng hàm mới.
  3. Thành phần con thấy một tham chiếu prop mới.
  4. Thành phần con re-render.

👉 Nếu thành phần con nặng, re-renders không cần thiết khiến UI cảm thấy chậm chạp.


Trong JS, hàm là đối tượng

javascript Copy
function a() {}
function b() {}
console.log(a === b); // false

→ Một hàm được tạo mới là một đối tượng khác mỗi lần.


✅ Tối ưu hóa với useCallback + React.memo

javascript Copy
const Child = React.memo(({ onClick }) => {
  console.log("Child render");
  return <button onClick={onClick}>Click</button>;
});

function Parent() {
  const [count, setCount] = useState(0);

  const handleClick = useCallback(() => {
    setCount(c => c + 1);
  }, []);

  return <Child onClick={handleClick} />;
}

useCallback — Các điểm chính

  • Ghi nhớ một hàm để tham chiếu của nó vẫn ổn định
  • Ngăn chặn re-renders không cần thiết của các thành phần con khi truyền callbacks
  • Ổn định các hàm được sử dụng trong mảng phụ thuộc của useEffect
  • Hoạt động tốt nhất khi kết hợp với React.memo

5. useEffect × useReducer: Xử Lý Phụ Thuộc Hàm & Trạng Thái Đa Bước

Ví dụ về phụ thuộc hàm

javascript Copy
function Child({ onData }) {
  useEffect(() => {
    const id = setInterval(() => {
      onData(new Date().toLocaleTimeString());
    }, 1000);
    return () => clearInterval(id);
  }, [onData]); // bao gồm phụ thuộc hàm
}

👉 Trong cha, sử dụng useCallback để ổn định onData.


Chuyển tiếp đa bước với useReducer

javascript Copy
const initialState = { status: "idle", data: null, error: null };

function reducer(state, action) {
  switch (action.type) {
    case "FETCH_START":   return { status: "loading", data: null, error: null };
    case "FETCH_SUCCESS": return { status: "success", data: action.payload, error: null };
    case "FETCH_ERROR":   return { status: "error", data: null, error: action.error };
    default:              return state;
  }
}

useReducer — Các điểm chính

  • Tuyệt vời cho trạng thái đa bước phức tạp
  • Đặt tên cho các hành động để làm rõ logic
  • Thường dispatch từ bên trong useEffect
  • Lý tưởng cho các biểu mẫu, fetch dữ liệu, quy trình bước
  • Thường kết hợp với các phụ thuộc hàm

6. Custom Hooks

javascript Copy
function useWindowWidth() {
  const [width, setWidth] = useState(window.innerWidth);

  useEffect(() => {
    const handler = () => setWidth(window.innerWidth);
    window.addEventListener("resize", handler);
    return () => window.removeEventListener("resize", handler);
  }, []);

  return width;
}

Custom Hooks — Các điểm chính

  • Kết hợp nhiều hooks để xây dựng logic tái sử dụng
  • Phải được đặt tên bắt đầu bằng use
  • Trích xuất logic không gắn liền với UI để tái sử dụng
  • Hoàn hảo cho các tác vụ chung (kích thước cửa sổ, fetching, kiểm tra xác thực)

7. Bảng Tóm Tắt Các Hooks

Hook Cách Sử Dụng Chính Cảnh Báo / Cạm Bẫy
useState Quản lý trạng thái đơn giản setState thay thế giá trị. Spread các đối tượng khi cập nhật.
useEffect Tác dụng phụ (fetching, đăng ký, DOM) Phụ thuộc sai gây ra stale closures hoặc vòng lặp vô hạn.
useContext Tránh prop drilling Giá trị thay đổi re-render tất cả người tiêu dùng.
useRef Tham chiếu DOM / giá trị thay đổi không phải UI Cập nhật không re-render. Hữu ích chống lại stale closures.
useCallback Ghi nhớ các hàm / tham số ổn định Các hàm là đối tượng; các hàm mới re-render các thành phần con. Sử dụng với React.memo.
useReducer Trạng thái phức tạp (chuyển tiếp đa bước) Cần thiết lập nhiều hơn (hành động), nhưng rõ ràng và dễ đọc hơn trạng thái.
Custom Hooks Chia sẻ & tái sử dụng logic Phải bắt đầu bằng use. Tốt nhất cho hành vi tái sử dụng không gắn liền với UI.

📌 Cách chọn

  • Tính năng nhỏ → useState + useEffect
  • Giá trị chia sẻ toàn cầu → useContext
  • Vấn đề hiệu suất từ các hàm props → useCallback + React.memo
  • Quy trình phức tạp → useReducer
  • Logic tái sử dụng → Custom hooks
Gợi ý câu hỏi phỏng vấn
Không có dữ liệu

Không có dữ liệu

Bài viết được đề xuất
Bài viết cùng tác giả

Bình luận

Chưa có bình luận nào

Chưa có bình luận nào