Cách ghi nhớ các hàm và giá trị trong JavaScript và React

Memoization là một kỹ thuật tối ưu hóa, tương tự như bộ nhớ đệm. Nó hoạt động bằng cách lưu trữ các kết quả trước đó của một lệnh gọi hàm và sử dụng các kết quả đó vào lần chạy hàm tiếp theo. Nó đặc biệt hữu ích trong các ứng dụng nặng về tính toán, lặp lại các lệnh gọi hàm trên cùng một tham số.
Bạn có thể sử dụng ghi nhớ trong JavaScript thuần túy và cả trong React, theo một vài cách khác nhau.
Mục Lục
Ghi nhớ trong JavaScript
Để ghi nhớ một hàm trong JavaScript, bạn cần lưu trữ kết quả của hàm đó trong bộ nhớ cache. Bộ nhớ đệm có thể là một đối tượng với các đối số là khóa và kết quả là giá trị.
Khi bạn gọi hàm này, trước tiên nó sẽ kiểm tra xem kết quả có trong bộ đệm hay không trước khi chạy. Nếu đúng, nó sẽ trả về kết quả được lưu trong bộ nhớ cache. Nếu không, nó sẽ thực thi.
Hãy xem xét chức năng này:
function square(num) {
return num * num
}
Hàm nhận vào một đối số và trả về bình phương của nó.
Để chạy hàm, hãy gọi nó bằng một số như sau:
square(5)
Với 5 là đối số, square () sẽ chạy khá nhanh. Tuy nhiên, nếu bạn tính bình phương của 70.000, sẽ có một độ trễ đáng chú ý. Tuy nhiên, không nhiều nhưng là một sự chậm trễ. Bây giờ, nếu bạn gọi hàm nhiều lần và vượt qua 70.000, bạn sẽ gặp phải độ trễ trong mỗi cuộc gọi.
Bạn có thể loại bỏ sự chậm trễ này bằng cách sử dụng ghi nhớ.
const memoizedSquare = () => {
let cache = {};
return (num) => {
if (num in cache) {
console.log('Reusing cached value');
return cache[num];
} else {
console.log('Calculating result');
let result = num * num;// cache the new result value for next time
cache[num] = result;
return result;
}
}
}
Trong ví dụ này, hàm kiểm tra xem nó đã tính kết quả trước đó hay chưa, bằng cách kiểm tra xem nó có tồn tại trong đối tượng bộ đệm hay không. Nếu nó có nó sẽ trả về giá trị đã được tính toán.
Khi hàm nhận một số mới, nó sẽ tính toán một giá trị mới và lưu kết quả vào bộ nhớ đệm trước khi trả về.
Một lần nữa, ví dụ này khá đơn giản, nhưng nó giải thích cách ghi nhớ sẽ hoạt động như thế nào để cải thiện hiệu suất của một chương trình.
Bạn chỉ nên ghi nhớ các hàm thuần túy. Các hàm này trả về cùng một kết quả khi bạn chuyển các đối số giống nhau vào. Nếu bạn sử dụng ghi nhớ trên các hàm không tinh khiết, bạn sẽ không cải thiện hiệu suất mà còn tăng chi phí của mình. Đó là bởi vì bạn chọn tốc độ trên bộ nhớ mỗi khi bạn ghi nhớ một hàm.
Ghi nhớ trong React
Nếu bạn đang tìm cách tối ưu hóa các thành phần React, React cung cấp khả năng ghi nhớ thông qua hook useMemo (), React.memo và useCallBack ().
Sử dụng useMemo ()
useMemo () là một React hook chấp nhận một hàm và một mảng phụ thuộc.
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
Nó ghi nhớ giá trị được trả về từ hàm đó. Các giá trị trong mảng phụ thuộc ra lệnh khi hàm được thực thi. Chỉ khi chúng thay đổi thì chức năng mới được thực thi trở lại.
Ví dụ: thành phần Ứng dụng sau đây có giá trị được ghi nhớ được gọi là kết quả.
import { useMemo } from "react"
function App(value) {
const square = (value) => {
return value * value
}
const result = useMemo(
() => square(value),
[ value ]
);
return (
<div>{result(5)}</div>
)
}
Thành phần ứng dụng gọi square () trên mỗi lần hiển thị. Hiệu suất sẽ giảm nếu thành phần Ứng dụng được hiển thị nhiều lần do thay đổi đạo cụ React hoặc cập nhật trạng thái, đặc biệt nếu hàm square () đắt tiền.
Tuy nhiên, vì useMemo () lưu trữ các giá trị trả về, nên hàm bình phương không được thực thi trong mỗi lần kết xuất lại trừ khi các đối số trong mảng phụ thuộc thay đổi.
Sử dụng React.memo ()
React.memo () là một thành phần bậc cao hơn chấp nhận một thành phần React và một hàm làm đối số. Hàm xác định thời điểm nên cập nhật thành phần.
Chức năng này là tùy chọn và nếu không được cung cấp, React.memo sẽ tạo một bản sao so sánh nông của các đạo cụ hiện tại của thành phần với các đạo cụ trước đó của nó. Nếu các đạo cụ khác nhau, nó sẽ kích hoạt một bản cập nhật. Nếu các đạo cụ giống nhau, nó sẽ bỏ qua việc kết xuất lại và sử dụng lại các giá trị đã ghi nhớ.
Hàm tùy chọn chấp nhận đạo cụ trước đó và đạo cụ tiếp theo làm đối số. Sau đó, bạn có thể so sánh rõ ràng các đạo cụ này để quyết định có cập nhật thành phần hay không.
React.memo(Component, [areEqual(prevProps, nextProps)])
Trước tiên, hãy xem một ví dụ không có đối số hàm tùy chọn. Dưới đây là một thành phần được gọi là Nhận xét chấp nhận tên và đạo cụ email.
function Comments ({name, comment, likes}) {
return (
<div>
<p>{name}</p>
<p>{comment}</p>
<p>{likes}</p>
</div>
)
}
Thành phần nhận xét được ghi nhớ sẽ có React.memo bao quanh nó như thế này:
const MemoizedComment = React.memo(Comment)
Bạn có thể gọi sau đó gọi nó giống như bất kỳ thành phần React nào khác.
<MemoizedComment name="Mary" comment="Memoization is great" likes=1/>
Nếu bạn muốn tự mình thực hiện so sánh đạo cụ, hãy chuyển hàm sau vào React.memo làm đối số thứ hai.
import React from "react"
function checkCommentProps(prevProps, nextProps) {
return prevProps.name === nextProps.name
&& prevProps.comment === nextProps.comment
&& prevProps.likes === nextProps.likes
}const MemoizedComment = React.memo(Comments, checkCommentProps)
Nếu checkProfileProps trả về true, thành phần không được cập nhật. Nếu không, nó sẽ được hiển thị lại.
Chức năng tùy chỉnh rất hữu ích khi bạn muốn tùy chỉnh kết xuất lại. Ví dụ: bạn có thể sử dụng nó để cập nhật thành phần Nhận xét chỉ khi số lượt thích thay đổi.
Không giống như hook useMemo () chỉ ghi nhớ giá trị trả về của một hàm, React.memo ghi nhớ toàn bộ hàm.
Chỉ sử dụng React.memo cho các thành phần thuần túy. Ngoài ra, để giảm chi phí so sánh, chỉ ghi nhớ các thành phần có đạo cụ thay đổi thường xuyên.
Sử dụng useCallBack ()
Bạn có thể sử dụng hook useCallBack () để ghi nhớ các thành phần của hàm.
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
Hàm chỉ được cập nhật khi các giá trị trong mảng phụ thuộc thay đổi. Hook hoạt động giống như callback useMemo (), nhưng nó ghi nhớ thành phần hàm giữa các lần hiển thị thay vì ghi nhớ các giá trị.
Hãy xem xét ví dụ sau về một hàm được ghi nhớ gọi một API.
import { useCallback, useEffect } from "react";
const Component = () => {
const getData = useCallback(() => {
console.log('call an API');
}, []);
useEffect(() => {
getData();
}, [getData]);
};
Hàm getData () được gọi trong useEffect sẽ chỉ được gọi lại khi giá trị getData thay đổi.
Bạn có nên ghi nhớ?
Trong hướng dẫn này, bạn đã biết ghi nhớ là gì, lợi ích của nó và cách triển khai nó trong JavaScript và React. Tuy nhiên, bạn nên biết rằng React đã rất nhanh. Trong hầu hết các trường hợp, các thành phần hoặc giá trị ghi nhớ làm tăng thêm chi phí so sánh và không cải thiện hiệu suất. Bởi vì điều này, chỉ ghi nhớ các thành phần đắt tiền.
React 18 cũng giới thiệu các hook mới như useId, useTransition và useInsertionEffect. Bạn có thể sử dụng những thứ này để cải thiện hiệu suất và trải nghiệm người dùng của các ứng dụng React.