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


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




React cung cấp cho chúng ta 2 hooks là useMemo 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.




Ý 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.














Hiju Blog

I'm HiJu

Post a Comment

Previous Post Next Post