Khi nào dùng useMemo và useCallback - Phần 2

buiminh15

mikebui

Posted on May 10, 2022

Khi nào dùng useMemo và useCallback - Phần 2

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

Còn với useMemo ?!

useMemo tương tự như useCallback ngoại trừ nó cho phép bạn áp dụng ghi nhớ cho bất kỳ loại giá trị nào (không chỉ các hàm). Nó thực hiện điều này bằng cách chấp nhận một hàm trả về giá trị và sau đó hàm đó chỉ được gọi khi giá trị cần được truy xuất (điều này thường chỉ xảy ra một lần mỗi khi một phần tử trong mảng phụ thuộc thay đổi giữa các lần hiển thị).

Vì vậy, nếu tôi không muốn khởi tạo mảng InitialCandies đó mỗi lần hiển thị, tôi có thể thực hiện thay đổi này:

const initialCandies = ['snickers', 'skittles', 'twix', 'milky way']
// thay thế code trên bằng code dưới
const initialCandies = React.useMemo(
  () => ['snickers', 'skittles', 'twix', 'milky way'],
  [],
)
Enter fullscreen mode Exit fullscreen mode

Và rồi đoạn code thay thế có thể giải quyết vấn đề array re-render, nhưng sự đánh đổi việc tránh re-render trên với chi phí cho bộ nhớ thực sự không đáng. Trên thực tế, có lẽ sẽ tệ hơn khi sử dụng useMemo cho việc này vì một lần nữa chúng ta đang thực hiện một cuộc gọi hàm và mã đó đang thực hiện các phép gán thuộc tính, v.v.

Trong tình huống cụ thể này, điều tốt hơn nữa là thực hiện thay đổi này: ( để phần array ngoài function CandyDispenser để tránh việc re-render)

const initialCandies = ['snickers', 'skittles', 'twix', 'milky way']
function CandyDispenser() {
  const [candies, setCandies] = React.useState(initialCandies)
Enter fullscreen mode Exit fullscreen mode

Nhưng đôi khi bạn không có được may mắn trên vì đôi khi giá trị được lấy từ props hoặc các biến khác được khởi tạo trong phần thân của hàm.

Điều đáng nói là việc tối ưu hay không tối ưu không phải là vấn đề nghiêm trọng. Lợi ích của việc tối ưu đoạn code đó là rất nhỏ, vì vậy CÁCH TỐT HƠN là dành thời gian của bạn cho việc làm cho sản phẩm của bạn tốt hơn.

Vấn đề ở đây là gì?

Vấn đề là đây:

Tối ưu hóa hiệu suất không phải miễn phí. Việc này LUÔN đi kèm với một chi phí nhưng KHÔNG phải lúc nào việc tối ưu hóa cũng đủ để bù đắp chi phí đó.

Do đó, hãy tối ưu hóa một cách có trách nhiệm.

Vậy khi nào tôi nên sử dụngMemo và sử dụngCallback?

Có những lý do cụ thể mà cả hai hook này đều được tích hợp sẵn trong React:

  1. Bình đẳng tham chiếu (Referential equality)
  2. Tính toán phức tạp
Referential equality

Nếu bạn chưa quen với JavaScript / lập trình, sẽ không mất nhiều thời gian trước khi bạn tìm hiểu lý do tại sao lại như vậy:

true === true // true
false === false // true
1 === 1 // true
'a' === 'a' // true

{} === {} // false
[] === [] // false
() => {} === () => {} // false

const z = {}
z === z // true

// NOTE: React actually uses Object.is, but it's very similar to ===
Enter fullscreen mode Exit fullscreen mode

Tôi sẽ không đi quá sâu vào vấn đề này, nhưng đủ để nói rằng khi bạn khởi tạo một object bên trong component, sự tham chiếu tới object này sẽ là khác nhau ở mỗi lần render (ngay cả khi object có tất cả các thuộc tính giống nhau với tất cả các giá trị giống nhau).

Có hai tình huống về bình đẳng tham chiếu trong React, chúng ta hãy xem xét từng tình huống một.

Dependencies lists

Hãy xem lại một ví dụ.

function Foo({bar, baz}) {
  const options = {bar, baz}
  React.useEffect(() => {
    buzz(options)
  }, [options]) // muốn re-run mỗi khi bar và baz thay đổi
  return <div>foobar</div>
}

function Blub() {
  return <Foo bar="bar value" baz={3} />
}
Enter fullscreen mode Exit fullscreen mode

Lý do điều này có vấn đề là vì useEffect sẽ thực hiện kiểm tra tính bình đẳng tham chiếu trên options giữa mọi lần hiển thị và nhờ cách hoạt động của JavaScript, options sẽ luôn mới (vì options là object và tham chiếu là khác nhau giữa mỗi lần render), vì vậy khi React kiểm tra xem options có thay đổi giữa các lần hiển thị hay không, nó sẽ luôn đánh giá thành true, nghĩa là i useEffect callback sẽ được gọi sau mỗi lần hiển thị thay vì chỉ khi barbaz thay đổi.

Có hai điều chúng ta có thể làm để khắc phục điều này:

// option 1
function Foo({bar, baz}) {
  React.useEffect(() => {
    const options = {bar, baz}
    buzz(options)
  }, [bar, baz]) // we want this to re-run if bar or baz change
  return <div>foobar</div>
}
Enter fullscreen mode Exit fullscreen mode

Cách trên là cách tôi sẽ dùng khi tôi gặp trường hợp trên trong các project thực tế.

Nhưng có một tình huống và cách trên sẽ không dùng được: Nếu bar hoặc baz (không phải là primative) là các object / mảng / function / vv :

function Blub() {
  const bar = () => {}
  const baz = [1, 2, 3]
  return <Foo bar={bar} baz={baz} />
}
Enter fullscreen mode Exit fullscreen mode

Cách trên chỉ đúng khi biến dùng thuộc loại primitive (tìm hiểu primitive types and reference types)

Đây chính là lý do tại sao useCallbackuseMemo tồn tại. Vì vậy, đây là cách bạn khắc phục điều này:

function Foo({bar, baz}) {
  React.useEffect(() => {
    const options = {bar, baz}
    buzz(options)
  }, [bar, baz])
  return <div>foobar</div>
}

function Blub() {
  const bar = React.useCallback(() => {}, [])
  const baz = React.useMemo(() => [1, 2, 3], [])
  return <Foo bar={bar} baz={baz} />
}
Enter fullscreen mode Exit fullscreen mode

useCallback và useMemo dùng cho reference types

💖 💪 🙅 🚩
buiminh15
mikebui

Posted on May 10, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related