Nguyên tắc sử dụng Hook

Hook là một tính năng mới từ React 16.8. Nó cho phép sử dụng state và các tính năng khác của React mà không cần viết dạng class

Hook là các function JavaScript, có những quy luật bạn cần phải tuân theo khi sử dụng. Chúng tôi có một plugin linter để đảm bảo các luật này luôn luôn được áp dụng đúng:

Chỉ gọi Hook ở trên cùng

Không gọi hook bên trong loop, câu điều kiện, hay các function lồng với nhau. Thay vì đó, luôn sử dụng Hook ở phần trên cùng của function. Với cách này, bạn đảm bảo các Hook được gọi theo đúng thứ tự trong các lần render. Nó cho phép React có được đúng state giữa nhiều lần gọi useStateuseEffect. (Nếu bạn có thắc mắc, chúng tôi sẽ giải thích trong phần giải thích cụ thể hơn bên dưới.)

Chỉ gọi Hook từ React Function

Không gọi Hook từ mà function JavaScript. Thay vì đó, bạn có thể:

  • ✅ Gọi Hook từ React function components.
  • ✅ Gọi Hook từ custom Hook (Chúng ta sẽ học ở trang sau).

Khi tuân theo quy luật này, chúng ta đảm bảo tất cả logic trong 1 component rõ ràng nhất.

ESLint Plugin

Chúng tôi có cung cấp plugin ESLint tên là eslint-plugin-react-hooks đảm bảo 2 luật này luôn được áp dụng. Nếu thích bạn có thể thêm vào project:

npm install eslint-plugin-react-hooks --save-dev
// cài đặt ESLint
{
  "plugins": [
    // ...
    "react-hooks"
  ],
  "rules": {
    // ...
    "react-hooks/rules-of-hooks": "error", // Kiểm tra rule của Hook
    "react-hooks/exhaustive-deps": "warn" // Kiểm tra effect dependency
  }
}

Trong tương lai, chúng tôi có định sẽ gộp luôn 2 plugin này vào trong Create React App và các bộ công cụ tương tự

Bạn có thể bỏ qua và đọc tiếp trang chỉ dẫn tạo custom Hook bây giờ. Ở đây, chúng ta sẽ tiếp tục giải thích lý do đằng sau những quy luật này.

Giải thích

Như chúng ta đã học trước đây, chúng ta có thể sử dụng nhiều state hoặc nhiều effect trên một component

function Form() {
  // 1. Sử dụng state tên name
  const [name, setName] = useState('Mary');

  // 2. Sử dụng một effect lưu giá trị trên form
  useEffect(function persistForm() {
    localStorage.setItem('formData', name);
  });

  // 3. Sử dụng state tên surname
  const [surname, setSurname] = useState('Poppins');

  // 4. Sử dụng 1 effect cập nhập title
  useEffect(function updateTitle() {
    document.title = name + ' ' + surname;
  });

  // ...
}

Vậy làm sao React biết được state nào ứng với lúc gọi useState ? Câu trả lời là React dựa trên thứ tự Hook được gọi. Trong ví dụ trên, vì thứ tự Hook được gọi đúng theo lúc khai báo trong khi render:

// ------------
// Lần đầu render
// ------------
useState('Mary')           // 1. Khởi tạo  biết name với giá trị 'Mary'
useEffect(persistForm)     // 2. Thêm một effect 
useState('Poppins')        // 3. KHởi tạo biến surname với giá trị 'Poppins'
useEffect(updateTitle)     // 4. Thêm một effect cập nhập title

// -------------
// Lần gọi render thứ 2
// -------------
useState('Mary')           // 1. Đọc giá trị biến name
useEffect(persistForm)     // 2. Thay thế effect cũ
useState('Poppins')        // 3. Đọc giá trị biến surname
useEffect(updateTitle)     // 4. Thay thế effect cập nhập title

// ...

Miễn là thứ tự của Hook gọi đúng theo thứ tự giữa các lần render, React có thể liên kết local state với nhau. Chuyện gì sẽ xảy ra nếu chúng ta đặt câu gọi Hook (ví dụ như persistForm bên trong trong câu điều kiện?

  // 🔴 Chúng ta vi phạm nguyên tắc không đặt trong câu điều kiện
  if (name !== '') {
    useEffect(function persistForm() {
      localStorage.setItem('formData', name);
    });
  }

Trong lần đầu render, mệnh đề điều kiện name !== '' trả về true, Hook sẽ được gọi. Tuy nhiên, trong lần gọi tiếp theo, user có thể xóa giá trị trong form, việc này làm cho mệnh đề điều kiện trả về false, chúng ta bỏ qua câu gọi effect, thứ tự gọi Hook cũng thay đổi theo:

useState('Mary')           // 1. Đọc giá trị name
// useEffect(persistForm)  // 🔴 Hook bị bỏ qua!
useState('Poppins')        // 🔴 2 (thật ra là 3). Không đọc được giá trị surname
useEffect(updateTitle)     // 🔴 3 (thật ra là 4).  Không thể thay thế effect

React không biết được trả về gì cho useState ở lần 2. React tưởng là Hook thứ 2 trong component tương ứng với effect persistForm, cũng giống như lần render trước, tuy nhiên không còn đúng nữa. Kể từ lúc đó, tất cả những lần gọi Hook sau đều bỏ qua một bước, dẫn đến bug.

Đó là lý do tại sao phải gọi Hook ở trên cùng của component. Nếu chúng ta muốn chạy effect theo điều kiện, chúng ta có thể đặt điều kiện bên trong Hook:

  useEffect(function persistForm() {
    // 👍 Không phá vỡ nguyên tắc nữa
    if (name !== '') {
      localStorage.setItem('formData', name);
    }
  });

Lưu ý, bạn không cần lo lắng về vấn đề này nếu dùng lint. Giờ bạn cũng hiểu được tại sao Hook làm việc như vậy, và tại sao chúng ta lại có những nguyên tắc này.

Tiếp theo

Cuối cùng thì chúng ta cũng đã sẵn sàng học cách viết custom Hooks! Custom Hook cho phép chúng ta kết hợp các Hook được cung cấp bởi React với những gì bạn muốn, tái sử dụng những logic giữa các component.