Khi nào dùng useMemo và useCallback - Phần 3
Tags: react, reactnative, javascript
Published: 2022-05-10 08:41:26

Bài dịch từ trang:
https://kentcdodds.com/blog/usememo-and-usecallback
của tác giả Kent C. Dodds.

###### React.memo (và những người bạn)

Xem đoạn code sau: 

```
function CountButton({onClick, count}) {
  return <button onClick={onClick}>{count}</button>
}

function DualCounter() {
  const [count1, setCount1] = React.useState(0)
  const increment1 = () => setCount1(c => c + 1)

  const [count2, setCount2] = React.useState(0)
  const increment2 = () => setCount2(c => c + 1)

  return (
    <>
      <CountButton count={count1} onClick={increment1} />
      <CountButton count={count2} onClick={increment2} />
    </>
  )
}
```

Mỗi khi bạn click vào một trong các button, state của DualCounter sẽ thay đổi và do đó sẽ re-render, và re-render lại luôn cả 2 button CountButtons. 

Tuy nhiên, button thực sự cần render lại là button đã được click có phải không? Vì vậy, nếu bạn click vào button đầu tiên, cái thứ hai sẽ được re-render, nhưng không có gì thay đổi. Chúng tôi gọi đây là một "re-render không cần thiết."

BẠN KHÔNG NÊN TỐI ƯU HÓA CÁC RE-RENDER KHÔNG CẦN THIẾT. 

React RẤT nhanh và tôi nghĩ rằng có rất nhiều thứ để bạn làm với thời gian của mình hơn là tối ưu hóa những thứ như thế này. Trên thực tế, nhu cầu tối ưu hóa với những gì tôi sắp cho bạn thấy là rất hiếm đến mức tôi thực sự chưa bao giờ cần phải làm điều đó trong 3 năm tôi làm việc trên các sản phẩm PayPal.

Tuy nhiên, có những tình huống khi việc render có thể mất một lượng thời gian đáng kể (hãy nghĩ đến Đồ thị / Biểu đồ / Hoạt ảnh / v.v.) có tính tương tác cao. Nhờ bản chất thực dụng của React, thế nên có 1 cách giải quyết:

```
const CountButton = React.memo(function CountButton({onClick, count}) {
  return <button onClick={onClick}>{count}</button>
})
```

Bây giờ React sẽ chỉ re-render CountButton khi props của CountButton thay đổi! Nhưng chúng ta vẫn chưa xong. Hãy nhớ tới bình đẳng tham chiếu? Trong component của DualCounter, chúng ta đang định nghĩa các hàm increment1 và increment2 trong function của component (chính là function DualCounter()) , có nghĩa là mỗi khi DualCounter được re-render, các hàm đó sẽ là mới và do đó React sẽ re-render cả hai CountButtons.

Vì vậy, đây là một tình huống khác mà useCallback và useMemo thực sự có ích:


```
const CountButton = React.memo(function CountButton({onClick, count}) {
  return <button onClick={onClick}>{count}</button>
})

function DualCounter() {
  const [count1, setCount1] = React.useState(0)
  // đoạn này
  const increment1 = React.useCallback(() => setCount1(c => c + 1), [])

  const [count2, setCount2] = React.useState(0)
  // đoạn này
  const increment2 = React.useCallback(() => setCount2(c => c + 1), [])

  return (
    <>
      <CountButton count={count1} onClick={increment1} />
      <CountButton count={count2} onClick={increment2} />
    </>
  )
}
```

Bây giờ chúng ta có thể tránh được cái gọi là "re-render không cần thiết" của CountButton.

Tôi muốn nhắc lại rằng tôi **thực sự khuyên bạn không nên sử dụng React.memo (hoặc là PureComponent và shouldComponentUpdate) mà không có sự tính toán** bởi vì những tối ưu hóa đó đi kèm với chi phí và bạn cần đảm bảo rằng bạn biết chi phí đó sẽ không đáng kể so với lợi ích bạn nhận được (điều này khó xảy ra :D)

#### Tính toán phức tạp

Đây là lý do khác mà useMemo là một hook tích hợp cho React (lưu ý rằng cái này không áp dụng cho useCallback). Lợi ích của useMemo là bạn có thể nhận một giá trị như:

```
const a = {b: props.b}
```

Thay đổi như sau:

```
const a = React.useMemo(() => ({b: props.b}), [props.b])
```

Điều này không thực sự hữu ích cho trường hợp ở trên, nhưng hãy tưởng tượng rằng bạn có một hàm tính toán đồng bộ một giá trị mà việc tính toán rất mất thời gian:

```
function RenderPrimes({iterations, multiplier}) {
  const primes = calculatePrimes(iterations, multiplier)
  return <div>Primes! {primes}</div>
}
```

Điều đó có thể khá chậm với `iterations` hoặc `multiplier`. Bạn không thể làm cho phần cứng của người dùng nhanh hơn. Nhưng bạn có thể để **function RenderPrimes** không bao giờ phải tính cùng một giá trị hai lần liên tiếp, đó là những gì useMemo sẽ làm cho bạn:

```
function RenderPrimes({iterations, multiplier}) {
  const primes = React.useMemo(
    () => calculatePrimes(iterations, multiplier),
    [iterations, multiplier],
  )
  return <div>Primes! {primes}</div>
}
```

### Kết luận

Cần xem xét kĩ giữa chi phí và lợi ích nhận được khi sử dụng việc tối ưu hóa.

P.S Có thể nói việc tối ưu hóa đang là trend khi công ty nào cũng phỏng vấn điều này. Nhưng bạn có thực sự biết cách sử dụng nó.

Dự án mình làm 80% các function con đều bọc useMemo hoặc useCallback (của người khác)và mình là người duy nhất không sử dụng bất kì useMemo hay useCallback trong đoạn code của mình. Đừng theo trend khi bạn chưa hiểu cách sử dụng.