Dependency arrays trong React?
Tags: react, reactnative, javascript
Published: 2022-05-10 13:30:21

Bài viết được dịch từ:
https://devtrium.com/posts/dependency-arrays

### Dependency Array là gì?

Dependency arrays được dùng với React hook

Một số hook, như useEffect và useCallback có 2 đối số. Đối số đầu tiên là callback và cái thứ hai là dependency array. Nó có dạng một mảng các biến.

Trong ví dụ sau, [counter] là dependency array của hook useEffect:

```
useEffect(() => {
  console.log('Counter has value: ', counter);
}, [counter]);
```

Những Reac hook có dependency array là:

- useEffect
- useLayoutEffect
- useCallback
- useMemo
- useImperativeHandle (hầu như không dùng)

### Dependency array (Mảng phụ thuộc) được sử dụng để làm gì?

Mảng phụ thuộc về cơ bản cho hook biết "chỉ kích hoạt khi mảng phụ thuộc thay đổi". Trong ví dụ trên, nó có nghĩa là "chạy callback mỗi khi biến `counter` thay đổi".

Nếu bạn có nhiều phần tử trong một mảng phụ thuộc, hook sẽ kích hoạt nếu bất kỳ phần tử nào của mảng phụ thuộc thay đổi:

```
useEffect(() => {
  // chỉ chạy nếu `counter1` hoặc `counter2` thay đổi
  console.log('Either counter1 or counter2 changed (or both');
}, [counter1, counter2]);
```
Bạn có thể hỏi, nghĩa là gì khi hook "kích hoạt" mỗi khi một phần tử của mảng phụ thuộc thay đổi?

Điều đó phụ thuộc vào hook. Đối với hook useEffect, nó có nghĩa là chạy callback. Đối với hook useCallback, nó có nghĩa là thay đổi hàm được trả về bởi hook. Tương tự cho useMemo, là trả về giá trị.

#### Mảng phụ thuộc rỗng

Như tôi đã nói, mảng phụ thuộc điều khiển khi hook kích hoạt. Vậy điều gì sẽ xảy ra khi mảng phụ thuộc rỗng?

Đơn giản có nghĩa là hook sẽ chỉ kích hoạt một lần khi component được render lần đầu tiên. Vì vậy, đối với useEffect, nó có nghĩa là callback sẽ chạy một lần duy nhất trong vòng đời của component và không bao giờ chạy nữa.

```
useEffect(() => {
  console.log('I will run only once');
}, []);
```

Đây là một pattern rất phổ biến khi bạn muốn làm điều gì đó ở đầu vòng đời của component, chẳng hạn như thực hiện việc lấy data.

```
useEffect(() => {
  // chỉ chạy lần đầu tiên khi được mount vào DOM lần đầu
  fetch('https://yourapi.com');
}, []);
```

{% codesandbox flamboyant-shannon-ckieqz %}

 Mình có đoạn code mẫu ở trên để làm rõ ý của tác giả. Ở trong đoạn code trên có button update lại state. Khi state thay đổi thì sẽ render lại nhưng các bạn nhìn console.log thì chỉ có 1 lần render và lý do là mảng phụ thuộc là rỗng. Các bạn có thể bỏ mảng phụ thuộc để test thêm để thấy được sự khác nhau.

### Nên cho gì vào mảng phụ thuộc?

Quy tắc khá là đơn giản, một số ngoại lệ sẽ làm quy tắc khó hơn một chút.

Quy tắc là: nếu bất kỳ **biến nào được sử dụng bên trong hook nhưng được định nghĩa bên ngoài nó, thì nó sẽ nằm trong mảng phụ thuộc**. Điều đó đúng với cả biến cũng như hàm.

```
import { useEffect } from 'react';

const ExampleComponent = () => {
  const width = 200;

  const printToConsole = (value) => {
    console.log(value);
  };

  useEffect(() => {
    printToConsole(width);
  }, [width, printToConsole]);

  return <p>Hello World!</p>;
};
```

```
CHÚ Ý
Ví dụ trên thực sự sẽ dẫn đến lỗi, 
vì hàm printToConsole không được bao bọc trong useCallback. 
Điều đó sẽ dẫn đến việc kích hoạt useEffect trên 
mỗi lần render của ExampleComponent! 
Tôi sẽ chỉ ra một cách tốt hơn để làm điều này 
trong phần tiếp theo.
```

Như bạn có thể thấy trong ví dụ trên, cả biến `width` và hàm `printToConsole` đều được sử dụng trong hook `useEffect`, và do đó cần phải cho vào trong mảng.

Vì vậy, quy tắc khá là đơn giản, nhưng như tôi đã nói, có một số trường hợp ngoại lệ.

#### Biến được định nghĩa ngoài component

Nếu một giá trị được xác định bên ngoài một component, thì giá trị đó sẽ được cố định và sẽ không thay đổi khi ứng dụng đang chạy. Vì vậy, React không cần bạn thêm nó vào mảng phụ thuộc.
(Giờ thì mình mới hiểu cách khai báo biến khi dùng styled-component. Bạn nào dùng styled-component thì nhớ điều trên nhé 🥰)

```
import { useEffect } from 'react';

const width = 200;

const printToConsole = (value) => {
  console.log(value);
};

const ExampleComponent = () => {
  useEffect(() => {
    printToConsole(width);
  }, []);

  return <p>Hello World!</p>;
};
```
Sẽ là tốt hơn khi đặt mọi thứ có thể bên ngoài một component. các biến (như `width`) và các hàm tiện ích (như `printToConsole`) không nhất thiết là cần định nghĩa bên trong component.

#### Những hàm đã được tối ưu hóa rồi

Như chúng ta đã thấy, mục tiêu của mảng phụ thuộc là làm cho hook kích hoạt khi một trong các giá trị thay đổi. Không có ích gì khi đặt vào đó những thứ mà không thay đổi.

Và có một số giá trị mà React biết chắc chắn sẽ không thay đổi, bởi vì chính React đảm bảo điều đó. Một ví dụ về hành vi này là hàm setter được trả về bởi một hook useState:

```
const [counter, setCounter] = useState(0);
```

Hàm `setCounter` được React tối ưu hóa và sẽ không thay đổi. Vì vậy, ngay cả khi nó được sử dụng trong một hook với một mảng phụ thuộc, bạn không cần phải thêm nó vào mảng phụ thuộc.

```
import { useState, useEffect } from 'react';

const ExampleComponent = () => {
  const [counter, setCounter] = useState(0);

  useEffect(() => {
    setCounter(10);
  }, []);

  return <p>Counter is: {counter}</p>;
};
```

Điều này cũng đúng với hàm `dispatch` được trả về bởi hook `useReducer`.

```
CHÚ Ý

Nếu `setCounter` được truyền vào như một props từ HOC, 
bạn phải đưa nó vào mảng phụ thuộc vì 
React không biết nó đến từ đâu. 
Mặc dù vậy, nó sẽ không kích hoạt hook, 
vì vậy sẽ an toàn khi làm như vậy.
```
#### Refs

Refs khá khó khăn để hiểu, và có lẽ cần một bài viết riêng về Refs.

Nhưng liên quan đến mảng phụ thuộc, **hãy lưu ý rằng việc đặt một ref vào một mảng phụ thuộc là vô ích**. Không đi quá nhiều vào chi tiết, bởi vì giá trị thay đổi của ref sẽ không kích hoạt việc component re-render, vì vậy hook sẽ không kích hoạt, bất kể mảng phụ thuộc của nó là gì (mảng phụ thuộc chỉ được kiểm tra khi component re-render).

```
CHÚ Ý
Mặc dù việc đặt ref vào mảng phụ thuộc là vô ích, 
nhưng đừng đặt `ref.current` bên trong mảng phụ thuộc! 
Điều này sẽ dẫn đến lỗi!
```

#### Sử dụng es-lint để giúp bạn

Có rất nhiều trường hợp ngoại lệ, rất khó để nhớ hết. Nhưng đừng lo, ESLint luôn sẵn sàng trợ giúp bạn (Bạn đang sử dụng linter đúng không? Nếu không, bạn thực sự nên làm như vậy!).

Quy tắc `react-hooks / expustive-deps` sẽ cảnh báo bạn khi bạn đang làm sai với mảng phụ thuộc của mình. Nếu bạn muốn biết thêm thông tin, bạn có thể xem [tài liệu chính thức của React!](https://reactjs.org/docs/hooks-rules.html#eslint-plugin)

#### Cẩn thận với những gì bạn đặt trong mảng phụ thuộc

Khi có sự thay đổi trong mảng phụ thuộc có nghĩa là hook sẽ được kích hoạt, bạn phải cẩn thận với những gì bạn đưa vào mảng phụ thuộc của mình. **Đặc biệt, các hàm được định nghĩa bên trong component phải được bọc bằng `useCallback` và các giá trị bọc bằng `useMemo`!**