Tự học ReactJS: Sử dụng useCallback

Bạn có còn nhớ dependency list của useEffect không? Ôn lại một chút nhé:

React.useEffect(() => {
  window.localStorage.setItem('count', count)
}, [count]) // <-- that's the dependency list

Hãy nhớ rằng React sẽ dựa vào dependency list để so sánh, nếu giá trj trong dependency list thay đổi, React sẽ gọi callback trong useEffect (nếu bạn không truyền dependency list thì useEffect sẽ chạy mỗi khi component render) Điều đó bảo đảm rằng mỗi khi state thay đổi thì useEffect được sync lại (chạy lại)

Chuyện gì xảy ra nếu chúng ta truyền một function vào dependecy list.

const updateLocalStorage = () => window.localStorage.setItem('count', count)
React.useEffect(() => {
  updateLocalStorage()
}, []) // <-- truyền hàm updateLocalStorage vào dependency list?

Khi bạn sử dụng eslint để check rule cho hook:

// Your ESLint configuration
{
  "plugins": [
    // ...
    "react-hooks"
  ],
  "rules": {
    // ...
    "react-hooks/rules-of-hooks": "error", // Checks rules of Hooks
    "react-hooks/exhaustive-deps": "warn" // Checks effect dependencies
  }
}

Với cấu hình này, eslint sẽ nhắc chúng ta thêm hàm updateLocalStorage vào dependency list của useEffect. Vấn đề ở đây là mỗi khi component render lại thì hàm updateLocalStorage được tạo mới hoàn toàn và điều đó sẽ làm cho useEffect chạy lại (vì hàm này đã khác với lần trước), ví dụ trong useEffect chúng ta có setState gì đó thì component sẽ re-render lại, và cứ như vậy updateLocalStorage được tạo mới, chạy useEffect => setState => re-render => chạy useEffect … vòng lặp này cứ chạy mãi như thế.

Mục đích chính của useCallback là đảm bảo không tạo mới một function nếu các input của hàm đó không thay đổi.

const updateLocalStorage = React.useCallback(
  () => window.localStorage.setItem('count', count),
  [count], // <-- yup! Đây là dependency list!
)
React.useEffect(() => {
  updateLocalStorage()
}, [updateLocalStorage])

Chúng ta truyền vào một function cho useCallback và React trả về cho chúng ta một function giống như function mà chúng ta truyền vào, nhưng với một điều đặc biệt là trong các lần re-render tiếp theo, nếu các phần tử trong dependency ko thay đổi thì React sẽ không tạo mới một function mà trả về function updateLocalStorage như ban đầu.

import React, { useCallback } from 'react';

function MyComponent() {
  // handleClick is the same function object
  const handleClick = useCallback(() => {
    console.log('Clicked!');
  }, []);//không truyền bất cứ phần tử nào thì handleClick luôn luôn không bị tạo mới sau mỗi lần render

  // ...
}

Ngược lại, React sẽ trả về một function mới nếu có phần tử trong dependency list có thay đổi.

Một câu hỏi hay gặp là “Tại sao chúng ta không sử dụng useCallback cho mỗi function” Bạn có thể tham khảo bài viết này.

Không phải lúc nào sử dụng useCallback để tạo một function là cũng tốt, bởi vì cái gì cũng có một cái giá của nó. Nó làm cho code bạn thêm phức tạp và giống như kiểu bạn mua một cái laptop cấu hình thật khủng để chỉ gõ văn bản vậy. Phí lắm!!

Thực sự chi phí và độ phức tạp khi bạn sử dụng useCallback có đáng để đánh đổi cho app chạy nhanh hơn một xíu hay không.

Bạn có thể tham khảo bài viết của Dmitri Pavlutin để hiểu hơn về các trường hợp nên dùng useCallback và đừng lạm dụng useCallback.

Leave a Reply

Your email address will not be published. Required fields are marked *