Khi nào dùng useMemo và useCallback - Phần 1
Tags: react, javascript
Published: 2022-05-09 09:03:03

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

Có đoạn code như sau: 

```
function CandyDispenser() {
  const initialCandies = ['snickers', 'skittles', 'twix', 'milky way']
  const [candies, setCandies] = React.useState(initialCandies)
  const dispense = candy => {
    setCandies(allCandies => allCandies.filter(c => c !== candy))
  }
  return (
    <div>
      <h1>Candy Dispenser</h1>
      <div>
        <div>Available Candy</div>
        {candies.length === 0 ? (
          <button onClick={() => setCandies(initialCandies)}>refill</button>
        ) : (
          <ul>
            {candies.map(candy => (
              <li key={candy}>
                <button onClick={() => dispense(candy)}>grab</button> {candy}
              </li>
            ))}
          </ul>
        )}
      </div>
    </div>
  )
}
```
Giờ tôi muốn hỏi bạn và tôi muốn bạn nghĩ thật kĩ trước khi đi tiếp. Tôi sẽ thay đổi đoạn code trên và tôi muốn bạn nói cho tôi cái nào sẽ có performance tốt hơn.

Cái duy nhất tôi thay đổi là bọc hàm **dispense** vào trong **React.useCallback**

```
const dispense = React.useCallback(candy => {
  setCandies(allCandies => allCandies.filter(c => c !== candy))
}, [])
```

Và đây là đoạn code gốc:

```
const dispense = candy => {
  setCandies(allCandies => allCandies.filter(c => c !== candy))
}
```

Sau đây là câu hỏi của tôi, ở trong trường hợp này, đoạn code nào có performance tốt hơn? Tiếp tục nghĩ và chọn câu trả lời của bạn:

- đoạn code gốc
- useCallback

### Tại sao sử dụng useCallback thì tệ hơn?!

Chúng ta nghe rất nhiều rằng bạn nên dùng **React.useCallback** để cải thiện performance và 'các hàm inline function có thể có vấn đề cho performance', vậy làm thế nào để tốt hơn nếu không sử dụng usesCallback?

Hãy quay lại từ ví dụ trên, và thậm chí từ React và hãy suy ngẫm điều này: **Mỗi dòng code khi được thực thi đều đi kèm với cái giá của nó** . Hãy để tôi thay đổi lại đoạn code của phần **useCallback** một chút (chỉ di chuyển dòng code chứ không thay đổi gì cả) để có thể thấy rõ hơn: 

```
const dispense = candy => {
  setCandies(allCandies => allCandies.filter(c => c !== candy))
}
const dispenseCallback = React.useCallback(dispense, [])
```

Và đây là đoạn code gốc: 

```
const dispense = candy => {
  setCandies(allCandies => allCandies.filter(c => c !== candy))
}
```
Bạn thấy được điều gì? Hãy nhìn sự khác biệt:

```
const dispense = candy => {
  setCandies(allCandies => allCandies.filter(c => c !== candy))
}

// Dòng dưới này đây
const dispenseCallback = React.useCallback(dispense, [])
```

Đúng vậy, đoạn code giống nhau trừ phiên bản **useCallback** thì làm thêm nhiều việc hơn. Chúng ta không chỉ định nghĩa thêm function mà còn phải định nghĩa ra mảng ([]) và rồi gọi hàm **React.useCallback** mà chính nó đang thiết lập các thuộc tính / chạy qua các biểu thức logic, v.v.

Trong 2 trường hợp trên, **Javascript** phải phân bổ vùng nhớ cho hàm được định nghĩa cho mỗi lần render và phụ thuộc vào việc **useCallback** được thực thi, bạn có thể có thể nhận được nhiều phân bổ hơn cho việc định nghĩa hàm (thực tế không phải vậy nhưng vấn đề vẫn còn)

Tôi cũng muốn đề cập tới vấn đề là ở lần thứ 2 render của component, đoạn hàm **dispense** của bản gốc sẽ được thu dọn (giải phóng vùng của bộ nhớ) và sau đó sẽ có hàm mới được tạo. Tuy nhiên với **useCallback**, hàm **dispense** của bản gốc sẽ không cần thu dọn và hàm mới được tạo, và rồi bạn sẽ gặp vấn đề về bộ nhớ. 

Một lưu ý liên quan, nếu bạn có nhiều dependencies thì rất có thể React đang dựa vào các tham chiếu đến các hàm trước đó. Bởi vì _memorization_ thường có nghĩa là lưu giữ các giá trị cũ để trả lại trong trường hợp nhận được các dependencies giống như đã cho từ trước (dependencies không thay đổi). 

Điều đó có nghĩa là React cũng đang dựa vào các tham chiếu tới các dependencies cho việc equality check (điều này có thể tình cờ xảy ra do closure, nhưng dù sao cũng đáng để đề cập tới).

#### Hết phần 1 :)