Sử dụng useMemo và useCallback trong việc tối ưu code – ReactJS

Tram Ho

Trong bài viết lần này, chúng ta sẽ làm rõ về việc sử dụng useMemouseCallback để tối ưu hiệu năng trong ứng dụng React

Vấn đề

Thực sự useMemouseCallback có giúp tối ưu hiệu năng trong React App hay không hay chúng chỉ làm mọi thứ trở nên tồi tệ hơn?

Cách hoạt động

useCallback

useCallback là một Hook được giới thiệu trong React từ bản 16.8. useCallback trả về một function và một array chứa các dependencies (những biến số được truyền vào từ bên ngoài mà function này phụ thuộc khi chạy). useCallback sử dụng cơ chế memorization – ghi nhớ kết quả của một function vào trong memory và sẽ trả về function được ghi nhớ trong trường hợp các dependencies này không thay đổi.

Tại sao cần dùng useCallback?

Để ý ví dụ ở trên, chúng ta có ParentComponentChildComponent. Khi nhấn vào button ở ParentComponent, component này sẽ bị render lại do có sự thay đổi về state. Kéo theo đó, ChildComponent cũng bị render lại theo cho dù không có sự thay đổi gì (props truyền vào y nguyên)

useCallback được sinh ra để giải quyết vấn đề này. Nó sẽ ghi nhớ lại function được truyền vào và danh sách các dependencies. Lúc này, khi nhấn vào button, React sẽ render lại ParentComponent và so sánh xem props của ChildComponent có thay đổi không để render tiếp. Vì loggingStatus hoàn toàn không phụ thuộc vào dependency nào, nên giá trị của nó không thay đổi và ChildComponent sẽ không bị render lại.

So sánh hai trường hợp

Vâỵ cái giá của việc dùng useCallback là gì? Nếu để hai đoạn code đứng độc lập như vậy, chúng ta có thể khẳng định chắc chắn rằng useCallback sẽ tiêu tốn nhiều memory hơn, cụ thể:

  • useCallback phải ghi nhớ function được truyền vào trong nó
  • useCallback phải khởi tạo một array chứa những dependencies và ghi nhớ chúng

Cụ thể hơn, trong lần render component thứ 2, loggingStatus sẽ được xoá bỏ trên memory và khởi tạo lại. Tuy nhiên với useCallback, chúng ta vừa phải khởi tạo một function mới, đồng thời ghi nhớ function cũ đã có của lần render đầu. Điều tương tự cũng xảy ra với dependency array. Đứng dưới góc độ hiệu năng của memory, rõ ràng useCallback tiêu tốn nhiều tài nguyên hơn.

useMemo thì sao?

useMemo cũng có cơ chế hoạt động tương tự như useCallback, nhưng thay vì truyền vào một function, chúng ta có thể truyền vào một function trả về mọi kiểu dữ liệu mà chúng ta muốn ghi nhớ:

initialMyFruit sẽ chỉ được khởi tạo lại trong trường hợp giá trị của một dependency thay đổi.

Tóm lại

Cái gì cũng có cái giá của nó, vì vậy chúng ta cần sử dụng useCallbackuseMemo một cách thông thái để tránh bị đồng nghiệp chê cười.

Khi nào nên sử dụng useCallback và useMemo?

Có hai trường hợp như sau:

  • Referential equality
  • Những tính toán nặng

Referential equality

Vì không có từ nào ở Tiếng Việt đồng nghĩa với “Referential equality” nên mình sẽ giải thích concept này một chút. Làm sao để React biết được có sự thay đổi trong dependencies, state hoặc props để re-render component? React sẽ so sánh như sau:

Ở đây có một điểm đáng lưu ý là các object, array và function dù chứa nội dung giống nhau nhưng Javascript vẫn hiểu rằng đây là hai sự vật hiện tượng khác nhau. Ví dụ, bạn và hàng xóm đều có cùng một chiếc xe Mercedes-Maybach S650, dù hai chiếc xe này giống y hệt nhau, tuy nhiên không thể hiểu rằng chiếc xe của hàng xóm là của bạn được.

Tương tự như vậy, khi khởi tạo 2 object, array hay function, dù nội dung của chúng giống y hệt nhau, nhưng bản chất chúng thuộc về một vùng memory khác nhau nên vẫn sẽ là hai sự vật hiện tượng khác nhau.

Có hai trường hợp mà React sẽ sử dụng tới Referential equality:

Dependencies lists

Quay trở lại ví dụ ở trên với một chút thay đổi:

Lần này, chúng ta khởi tạo myObject từ ParentComponent và truyền xuống ChildComponent thông qua props. Bên cạnh đó, useEffect được dùng để chạy console.log(myObject) mỗi khi có sự thay đổi từ props được truyền vào. Khi ấn nút Click ở ParentComponent, dù không có sự thay đổi trong myObject, tuy nhiên ChildComponent vẫn bị render lại. Lý do cho việc này chính là cơ chế Referential equality của JavaScript.

Đây chính là lúc useMemo được sử dụng:

Những tính toán nặng, đồ thị, animation

Thật ra, trong hầu hết các trường hợp chúng ta sẽ không cần phải bận tâm tới việc component bị re-render do React có tốc độ xử lý rất nhanh và việc tối ưu này sẽ không đem lại quá nhiều sự khác biệt. Tuy nhiên có một trường hợp, useEffectuseMemo nên được dùng tới để hạn chế việc khởi tạo & execute những function “ngốn CPU” như ở dưới đây:

Kết luận

Việc sử dụng những API như useMemo hoặc useCallback để tối ưu code không có gì xấu. Tuy nhiên, lạm dụng “tối ưu code” mà không thực sự hiểu rõ vấn đề có thể sẽ khiến hiệu năng của code trở nên tồi tệ hơn. Bên cạnh đó, những concept như useMemouseCallback có thể làm code trở nên phức tạp và khó đọc, làm giảm hiệu quả khi làm việc chung giữa các thành viên trong team. Lời khuyên của mình là không nên tối ưu một cách máy móc, hãy thực sự hiểu và ứng dụng những API này một cách phù hợp trong từng tình huống khác nhau.

Reference & credits

Bài viết này có tham khảo ý tưởng của một số bài viết khác:

Chia sẻ bài viết ngay

Nguồn bài viết : Viblo