Sự khác biệt giữa hook useMemo và useCallback là gì?

Sự khác biệt giữa hook useMemo và useCallback là gì?

React cung cấp nhiều hook giúp quản lý trạng thái ứng dụng và các tính năng React khác trong các thành phần chức năng dễ dàng hơn. Hook cung cấp các tính năng thành phần lớp cho các thành phần chức năng và chúng không cần nhiều mã so với các thành phần lớp.

Hooks cũng giúp cuộc sống của bạn dễ dàng hơn bằng cách cung cấp một số tính năng tiện lợi. Trong số các hooks này, chúng ta có useMemovà useCallbackgiúp cải thiện hiệu suất trang web của bạn.

Trong hướng dẫn hôm nay, chúng ta sẽ thảo luận về cả hooks useMemovà useCallbackhooks. Bạn sẽ học được sự khác biệt giữa chúng và thời điểm sử dụng từng hooks.

useMemoHook

Hook ghi nhớuseMemo giá trị trả về của phép tính tốn kém giữa các lần kết xuất. Ghi nhớ có nghĩa là lưu trữ giá trị dưới dạng giá trị được lưu trong bộ nhớ đệm để giá trị không cần phải được tính toán lại (trừ khi cần thiết).

useMemolà một hook được sử dụng để tối ưu hóa hiệu suất render của bạn. Thông thường, khi bạn khai báo một biến bên trong một thành phần, nó sẽ được tạo lại trên mỗi lần render. Nếu nó lưu trữ giá trị trả về của một hàm, thì hàm đó sẽ được gọi mỗi lần render component của bạn.

Thông thường, điều này không phải là vấn đề. Nhưng, nếu hàm này tốn kém thì sao? Nếu mất nhiều thời gian hơn để thực thi thì sao? Hãy xem ví dụ sau:

function calculate() {
let result = 0;
for (let i = 0; i < 1000000000; i++) {
result += i;
}
return result;
}
function App1() {
const [count, setCount] = useState(0);
const value = calculate();
return (
<div className="App">
<button onClick={() => setCount(count + 1)}>Increment Count</button>
<p>Count: {count}</p>
</div>
);
}

Khi bạn nhấp vào ‘count’, phải mất vài giây để cập nhật trạng thái số lượng. Điều này là do hàm calculate run mỗi lần một thành phần được render lại sau khi thay đổi trạng thái.

Bây giờ, hãy tưởng tượng nếu có nhiều biến trạng thái trong thành phần, mỗi biến có mục đích riêng. Mỗi lần cập nhật trạng thái sẽ gây ra việc kết xuất lại và thực hiện hàm tốn kém này.

Các biến trạng thái này có thể hoàn toàn không liên quan đến phép tính tốn kém được thực hiện, điều này sẽ gây ra sự chậm trễ không cần thiết. Điều này sẽ ảnh hưởng đến hiệu suất của trang web của bạn và có thể dẫn đến trải nghiệm người dùng tồi tệ.

useMemocó thể giúp bạn giải quyết vấn đề này. Trước tiên chúng ta hãy hiểu cú pháp của nó:

const value = useMemo(expensiveFunction, [...dependencyArray])

Hook useMemophải được khai báo ở cấp cao nhất của thành phần của bạn. Nó lấy các đối số sau:

  • expensiveFunctionchứa phép tính tốn kém mà bạn muốn thực hiện. Nếu bạn đã khai báo hàm bên ngoài, chỉ truyền tham chiếu hàm, không có dấu ngoặc. Bạn cũng có thể truyền trực tiếp các hàm mũi tên.
  • dependencyArraychứa danh sách các phụ thuộc cho hook. Hàm tốn kém sẽ chỉ được gọi khi một trong các phụ thuộc này được cập nhật. Bạn có thể truyền các biến trạng thái hoặc prop phụ thuộc vào phép tính này. Bất kỳ cập nhật trạng thái nào khác sẽ không kích hoạt hàm.

Trong lần render đầu tiên, useMemotrả về kết quả expensiveFunctionvà lưu trữ kết quả. Trong các lần render tiếp theo, nó sẽ trả về giá trị được lưu trữ nếu không có sự phụ thuộc nào thay đổi. Nếu chúng thay đổi, thì nó sẽ gọi lại hàm.

Chúng ta hãy sử dụng điều này trong trường hợp của chúng ta:

const [dependentCount, setDependentCount] = useState(10);
const value = useMemo(calculate, [dependentCount]);
return (
<div className="App">
// ...
<button onClick={() => setDependentCount(dependentCount + 1)}>
Increment Dependent Count
</button>
<p>Dependent Count: {dependentCount}</p>
</div>
);

Chúng ta đã tạo một trạng thái khác dependentCountmà chúng ta cho là phụ thuộc vào phép tính tốn kém. Khi trạng thái này cập nhật và hiển thị thành phần, hàm calculatesẽ chạy.

Nhưng nếu bất kỳ trạng thái nào khác thay đổi, thì useMemosẽ trả về giá trị được lưu trong bộ nhớ đệm thay vì chạy lại hàm.

Hãy kiểm tra điều này bằng cách thêm a console.logvào bên trong hàm:

hình ảnh-28
Đầu ra với useMemo

Bây giờ, khi bạn nhấp vào “Increment Count”, việc kết xuất sẽ nhanh hơn vì hàm calculatekhông được gọi trong mọi lần kết xuất. Điều này cũng giống như trong mọi lần cập nhật trạng thái khác không được liệt kê trong mảng phụ thuộc useMemo.

Nhưng khi bạn nhấp vào “Increment dependent Count”, phải mất thời gian để hiển thị giá trị đã cập nhật. Điều này là do dependentCountnó là một phụ thuộc của useMemovà việc thay đổi nó sẽ gọi hàm tốn kém, do đó thành phần mất thời gian để hiển thị lại.

Theo cách này, với useMemo, bạn có thể kiểm soát việc thực thi một hàm tốn kém bằng cách chỉ gọi nó cho các bản cập nhật trạng thái thực sự cần giá trị được trả về. Điều này có thể cải thiện đáng kể hiệu suất ứng dụng của bạn.

Khi nào nên sử dụng useMemo:

  • Khi bạn có trạng thái phụ thuộc vào phép tính tốn kém nhưng không muốn chạy phép tính đó trên mọi lần kết xuất.
  • Khi bạn khai báo một mảng hoặc đối tượng bên trong một thành phần, tham chiếu của nó thay đổi trên mỗi lần kết xuất, mặc dù giá trị vẫn giữ nguyên. Việc bao bọc các giá trị bên trong useMemoduy trì tính bình đẳng tham chiếu và ngăn chặn việc kết xuất lại không cần thiết. Điều này rất cần thiết khi có useEffectsự phụ thuộc vào mảng hoặc đối tượng.
  • Khi bạn đang kết xuất danh sách, Array.mapkhông cần phải thay đổi trừ khi giá trị trạng thái nào đó thay đổi.

Cái useCallbackHook

Tương tự như useMemo, bạn cũng có thể sử dụng hook này để tối ưu hóa hiệu suất. useCallbackHook ghi nhớ một hàm gọi lại và trả về hàm đó.

Lưu ý rằng useCallbackhook sẽ ghi nhớ chính hàm đó, chứ không phải giá trị trả về của nó. useMemoLưu trữ đệm giá trị trả về của hàm để hàm không cần phải thực thi lại. useCallbackLưu trữ đệm định nghĩa hàm hoặc tham chiếu hàm.

Một hàm được khai báo bên trong một thành phần được tạo lại trên mỗi lần render thành phần, tương tự như một biến. Sự khác biệt là, nó được render với một tham chiếu khác nhau mỗi lần. Vì vậy, một thành useEffectphần phụ thuộc vào hàm này sẽ thực thi lại trên mỗi lần render. Một điều tương tự xảy ra với các thành phần con.

Chúng ta hãy lấy một ví dụ:

const App = () => {
const [count, setCount] = useState(0);
const [value, setValue] = useState("");
const handleClick = () => {
setValue("Kunal");
};
return (
<div className="App">
<button onClick={() => setCount(count + 1)}>Increment Count</button>
<p>Count: {count}</p>
<p>Value: {value}</p>
<SlowComponent handleClick={handleClick} />
</div>
);
};
const SlowComponent = React.memo(({ handleClick, value }) => {
// Intentially making the component slow
for (let i = 0; i < 1000000000; i++) {}
return (
<div>
<h1>Slow Component</h1>
<button onClick={handleClick}>Click Me</button>
</div>
);
});

Ở đây, chúng ta có a SlowComponentlà thành phần con của Appthành phần. Khi một thành phần cha được render, tất cả các thành phần con của nó đều được render, bất kể có bất kỳ thay đổi nào bên trong chúng hay không.

Để tránh việc render không cần thiết các thành phần con, chúng ta thường sử dụng hàm React.memo. Về cơ bản, hàm này sẽ lưu trữ bộ nhớ đệm thành phần và chỉ render lại nếu props của thành phần đó đã thay đổi.

Bây giờ, khi bạn nhấp vào ‘Increment Count’, vẫn mất nhiều thời gian để render, vì SlowComponentrender lại khi trạng thái thay đổi. Nhưng tại sao vậy? Chúng ta không thay đổi bất kỳ prop nào của nó.

Trên bề mặt, chúng ta có thể không thay đổi giá trị của handleClickprop. Nhưng vì các hàm được tạo lại với một tham chiếu khác, nên trên mỗi lần render của thành phần App, thành phần con của nó (tức là SlowComponent) sẽ render.

Để duy trì tính bình đẳng tham chiếu, chúng ta gói định nghĩa hàm này bên trong a useCallback.

Chúng ta hãy cùng tìm hiểu cú pháp của nó:

const cachedFn = useCallback(fn, [...dependencyArray])

useCallbackcó các lập luận sau:

  • fnlà hàm bạn muốn lưu vào bộ nhớ đệm. Đây là định nghĩa hàm bạn muốn tạo và có thể lấy bất kỳ đối số nào và trả về bất kỳ giá trị nào.
  • dependencyArraylà danh sách các phụ thuộc, các thay đổi kích hoạt việc tạo lại hàm. Bạn có thể truyền các giá trị trạng thái hoặc prop phụ thuộc vào hàm này.

Trong lần render đầu tiên, React tạo hàm (không gọi hàm) và lưu vào bộ nhớ đệm. Trong các lần render tiếp theo, hàm được lưu vào bộ nhớ đệm sẽ được trả về cho bạn. Hãy nhớ rằng, hook này trả về và lưu vào bộ nhớ đệm hàm chứ không phải giá trị trả về của hàm.

Chúng ta hãy sử dụng hook này trong ví dụ của chúng ta:

import { useCallback } from "react";
const App = () => {
// ...
const handleClick = useCallback(() => {
setValue("Kunal");
}, [value, setValue]);
// ...
};

Ở đây, chúng ta đã gói hàm bên trong a useCallbackvà truyền hai phụ thuộc liên quan đến hàm này.

Bây giờ, khi bạn nhấp vào ‘Increment Count’, việc kết xuất sẽ nhanh hơn nhiều. Điều này là do tham handleClickchiếu được lưu trong bộ nhớ đệm giữa các lần kết xuất và do đó, SlowComponentkhông kết xuất lại.

Nhưng khi bạn nhấp vào nút bên trong, SlowComponentnó sẽ render lại. Điều này là do khi valuetrạng thái thay đổi, handleClickphương thức được tạo lại và do đó các props của thành phần chậm đã thay đổi.

hình ảnh-33
Giá trị cần thời gian để hiển thị

Bạn có thể thêm nhiều trạng thái hơn vào Appthành phần và cập nhật chúng mà không làm giảm hiệu suất miễn là bạn không cập nhật valuetrạng thái của.

Khi nào sử dụnguseCallback

  • Khi bạn đã xác định trình xử lý sự kiện cho một phần tử bên trong thành phần của mình, hãy gói chúng bên trong a useCallbackđể tránh việc tạo lại trình xử lý sự kiện không cần thiết.
  • Khi bạn gọi một hàm bên trong a useEffect, bạn thường truyền hàm đó dưới dạng một dependency. Để tránh sử dụng useEffectkhông cần thiết trên mỗi lần render, hãy gói định nghĩa hàm bên trong a useCallback.
  • Nếu bạn đang viết một hook tùy chỉnh và nó trả về một hàm, bạn nên gói nó bên trong useCallback. Vì vậy, người dùng không cần phải lo lắng về việc tối ưu hóa hook – thay vào đó, họ có thể tập trung vào mã của riêng họ.

Sự khác biệt giữa useMemovàuseCallback

Chúng ta hãy tóm tắt sự khác biệt giữa hai loại hook này:

  • useMemolưu trữ đệm giá trị trả về của một hàm. useCallbacklưu trữ đệm định nghĩa của chính hàm đó.
  • useMemođược sử dụng khi bạn có một phép tính tốn kém mà bạn muốn tránh trong mỗi lần kết xuất.
  • useCallbackđược sử dụng để lưu trữ đệm một hàm nhằm tránh phải tạo lại hàm đó sau mỗi lần kết xuất lại.
  • useMemođảm bảo rằng một hàm đắt tiền chỉ được gọi cho các giá trị trạng thái phụ thuộc vào nó.
  • useCallbacktạo ra các hàm ổn định duy trì cùng một tham chiếu giữa các lần kết xuất. Điều này tránh việc kết xuất không cần thiết các thành phần con.

Và đây là một vài điều nữa cần nhớ. Chỉ sử dụng các hook này nếu bạn muốn ghi nhớ các phép tính tốn kém hoặc ngăn chặn việc kết xuất lại không cần thiết. Không sử dụng useMemovà useCallbackở mọi nơi.

Đối với các hàm thông thường, các hook này không tạo ra nhiều khác biệt. Việc sử dụng quá nhiều sẽ khiến mã của bạn không thể đọc được. Thay vào đó, bạn có thể tìm ra những cách khác để cải thiện hiệu suất ứng dụng.  

Phần kết luận

useMemovà useCallbacklà những hook hữu ích trong React có thể giúp bạn tối ưu hóa hiệu suất của ứng dụng web. Điều quan trọng là phải hiểu sự khác biệt giữa hai cái này và cách sử dụng của chúng.

Trong bài viết này, chúng ta đã thảo luận về cách thức hoạt động của cả hai hook. useMemolưu trữ kết quả của một phép tính tốn kém, trong khi useCallbacklưu trữ tham chiếu hàm. Chúng ta cũng đã liệt kê các tình huống khi bạn nên sử dụng từng hook. Cả hai hook này cùng nhau có thể giúp trang web của bạn nhanh hơn.

Mình hy vọng bài viết này giúp làm sáng tỏ mọi sự nhầm lẫn. Nếu bạn có thêm câu hỏi hoặc bình luận về bài đăng, hãy liên hệ với mình. Mình rất muốn nghe các đề xuất. Hẹn gặp lại lần sau, tạm biệt!

Để lại một bình luận