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

Tác giả: Phạm Minh Khoa

React cung cấp cho chúng ta 2 hooks là useMemo và useCallback, chúng rất hữu ích khi bạn xử lý với những hoạt động phức tạp, tốn nhiều thời gian và tài nguyên của ứng dụng.

Nếu trong React component của bạn có chứa những hoạt động tốn tài nguyên (expensive operation), mỗi lần component được renders lại thì những tasks đó cũng sẽ được chạy lại khiến cho ứng dụng của bạn trở nên chậm chạp hơn. 2 hooks trên sẽ giúp tối ưu (optimize) ứng dụng của bạn bằng cách chạy những hoạt động tốn tài nguyên đó và lưu trữ kết quả (store result) của chúng lại trong cache. Khi component render lại trong lần tiếp theo thì chúng sẽ được chạy lại các hoạt động đó mà thay vào đó sẽ trả về luôn kết quả từ trong cache.

useMemo và useCallback

useMemo hoạt động thế nào?

Giả sử rằng chúng ta có 1 đoạn code xử lý hoạt động tốn thời gian và tài nguyên trong React component như sau:

function uselessExpensiveOperation(input) {
    const someBigArray = [];
    for (let i = 0; i < 5_000_000; i++) {
        someBigArray.push(input * i);
    }
    return someBigArray;
}

function SomeReactComponent() {
    const expensiveOperationResult = uselessExpensiveOperation(3);
    const output = expensiveOperationResult
        .slice(0, 5)
        .map(number => <li key={ number }>{ number }</li>);

    return <ul>{ output }</ul>;
}

function này sẽ chạy mất khoảng vài giây, nó sẽ trả về 1 mảng 5 triệu giá trị số phụ thuộc vào tham số đầu vào. Nếu bạn sử dụng hàm uselessExpensiveOperation trực tiếp trong 1 component React thì mỗi lần gọi xử lý render, nó sẽ gọi và chạy lại.

Trường hợp này chúng ta dùng useMemo để lưu trữ giá trị trả về trong cache như sau:

function SomeReactComponent() {
    const expensiveOperationResult = useMemo(
        function() {
            return uselessExpensiveOperation(3);
        },
        []
    );
    const output = expensiveOperationResult
        .slice(0, 5)
        .map(number => <li key={ number }>{ number }</li>);

    return <ul>{ output }</ul>;
}

useMemo nhận tham số đầu tiên là 1 function chứa hoạt động xử lý tốn tài nguyên (expensive operation), biến số thứ 2 là 1 mảng các phụ thuộc (dependencies). Nếu có sự thay đổi giá trị của bất cứ phụ thuộc nào thì React sẽ thực hiện việc xóa cache và chạy lại task tốn tài nguyên trên.

  Hướng dẫn sử dụng useMemo trong React
  React hook là gì và lợi ích mà React hook đem lại

Ý tưởng của mảng các phụ thuộc là những gì mà bạn cần thêm vào như các biến mà hoạt động tốn tài nguyên ấy cần để xử lý. Như trong ví dụ trên thì hoạt động tốn tài nguyên của chúng ta không cần bất cứ phụ thuộc nào nên mảng truyền vào để rỗng (empty array).

useCallback hook sử dụng như thế nào?

useCallback khá giống với useMemo nên khiến các bạn có thể dễ nhầm lẫn; điều khác biệt là nó lưu trữ các functions (hàm) trong cache thay vì lưu trữ kết quả (results).

function SomeReactComponent() {
    const cachedFunction = useCallback(
        function originalFunction() {
            return "some value";
        },
        []
    );

    return <div>{ cachedFunction() }</div>
}

useMemo cũng làm được như useCallback nhờ việc React cung cấp 1 function đặc biệt là React.memo, nó hoạt động giống như useMemo nhưng nó lưu trữ React components vào trong cache để tránh việc render lại không cần thiết. Nó hoạt động như sau:

const cachedComponent = React.memo(
    function SomeReactComponent(props) {
        return <div>Hello, { props.firstName }!</div>
    }
);

Component SomeReactComponent sẽ được lưu trữ trong cache cho đến khi 1 trong những props thay đổi, lúc đó nó sẽ được render lại và lưu trữ vào cache 1 lần nữa.

Tuy nhiên có 1 vấn đề ở đây mà bạn cần lưu ý: Nếu trong props có chứa 1 function mà nó được tạo ra trong component cha (parent components), lúc đó mỗi khi component cha render lại thì function đó (inner function) cũng sẽ được tạo lại, điều đó khiến cho nó được xem là 1 function khác trong khi code không hề thay đổi. Dẫn đến việc nó sẽ khiến components đã cached sẽ bị render lại không cần thiết.

Lúc này nếu bạn sử dụng hook useCallback thì vấn đề trên sẽ có thể được giải quyết. useCallback sẽ chỉ tạo function trong lần đầu tiên component được render. Khi component được render lại thì nó sẽ lấy function đó từ trong cache, và trả về đúng function cũ, điều đó giúp cho props sẽ không bị thay đổi và phải render lại không cần thiết.

Đừng tối ưu quá mức (Over Optimize)

Có nhiều bạn dev sử dụng 2 hooks trên (hoặc 1 vài kĩ thuật optimize khác) ngay cả khi chúng không cần thiết. Các bạn lưu ý mục đích của 2 hooks này sinh ra, bài toán nó giải quyết, trường hợp nó nên được sử dụng. Nó có thể khiến cho code của bạn trở lên phức tạp hơn, khó maintain hơn và trong 1 vài trường hợp thậm chí nó còn hoạt động tồi hơn.

Bạn hãy nên áp dụng những kĩ thuật tối ưu hiệu năng sau khi phát hiện ra vấn đề, khi 1 thứ gì đó không chạy nhanh như bạn mong muốn thì hãy tìm hiểu xem nút thắt cổ chai (bottleneck) nằm ở đâu và tối ưu hóa phần đó.

Cảm ơn các bạn đã đọc.

Bài viết gốc được đăng tải tại anywayblogs.com

Xem thêm:

Tham khảo ngay Top IT job hot có tại TopDev!