Khi nào dùng useMemo và useCallback - Phần 2
mikebui
Posted on May 10, 2022
Bài dịch từ trang:
https://kentcdodds.com/blog/usememo-and-usecallback
của tác giả Kent C. Dodds.
Còn với useMemo ?!
useMemo tương tự như useCallback ngoại trừ nó cho phép bạn áp dụng ghi nhớ cho bất kỳ loại giá trị nào (không chỉ các hàm). Nó thực hiện điều này bằng cách chấp nhận một hàm trả về giá trị và sau đó hàm đó chỉ được gọi khi giá trị cần được truy xuất (điều này thường chỉ xảy ra một lần mỗi khi một phần tử trong mảng phụ thuộc thay đổi giữa các lần hiển thị).
Vì vậy, nếu tôi không muốn khởi tạo mảng InitialCandies đó mỗi lần hiển thị, tôi có thể thực hiện thay đổi này:
const initialCandies = ['snickers', 'skittles', 'twix', 'milky way']
// thay thế code trên bằng code dưới
const initialCandies = React.useMemo(
() => ['snickers', 'skittles', 'twix', 'milky way'],
[],
)
Và rồi đoạn code thay thế có thể giải quyết vấn đề array re-render, nhưng sự đánh đổi việc tránh re-render trên với chi phí cho bộ nhớ thực sự không đáng. Trên thực tế, có lẽ sẽ tệ hơn khi sử dụng useMemo cho việc này vì một lần nữa chúng ta đang thực hiện một cuộc gọi hàm và mã đó đang thực hiện các phép gán thuộc tính, v.v.
Trong tình huống cụ thể này, điều tốt hơn nữa là thực hiện thay đổi này: ( để phần array ngoài function CandyDispenser để tránh việc re-render)
const initialCandies = ['snickers', 'skittles', 'twix', 'milky way']
function CandyDispenser() {
const [candies, setCandies] = React.useState(initialCandies)
Nhưng đôi khi bạn không có được may mắn trên vì đôi khi giá trị được lấy từ props hoặc các biến khác được khởi tạo trong phần thân của hàm.
Điều đáng nói là việc tối ưu hay không tối ưu không phải là vấn đề nghiêm trọng. Lợi ích của việc tối ưu đoạn code đó là rất nhỏ, vì vậy CÁCH TỐT HƠN là dành thời gian của bạn cho việc làm cho sản phẩm của bạn tốt hơn.
Vấn đề ở đây là gì?
Vấn đề là đây:
Tối ưu hóa hiệu suất không phải miễn phí. Việc này LUÔN đi kèm với một chi phí nhưng KHÔNG phải lúc nào việc tối ưu hóa cũng đủ để bù đắp chi phí đó.
Do đó, hãy tối ưu hóa một cách có trách nhiệm.
Vậy khi nào tôi nên sử dụngMemo và sử dụngCallback?
Có những lý do cụ thể mà cả hai hook này đều được tích hợp sẵn trong React:
- Bình đẳng tham chiếu (Referential equality)
- Tính toán phức tạp
Referential equality
Nếu bạn chưa quen với JavaScript / lập trình, sẽ không mất nhiều thời gian trước khi bạn tìm hiểu lý do tại sao lại như vậy:
true === true // true
false === false // true
1 === 1 // true
'a' === 'a' // true
{} === {} // false
[] === [] // false
() => {} === () => {} // false
const z = {}
z === z // true
// NOTE: React actually uses Object.is, but it's very similar to ===
Tôi sẽ không đi quá sâu vào vấn đề này, nhưng đủ để nói rằng khi bạn khởi tạo một object bên trong component, sự tham chiếu tới object này sẽ là khác nhau ở mỗi lần render (ngay cả khi object có tất cả các thuộc tính giống nhau với tất cả các giá trị giống nhau).
Có hai tình huống về bình đẳng tham chiếu trong React, chúng ta hãy xem xét từng tình huống một.
Dependencies lists
Hãy xem lại một ví dụ.
function Foo({bar, baz}) {
const options = {bar, baz}
React.useEffect(() => {
buzz(options)
}, [options]) // muốn re-run mỗi khi bar và baz thay đổi
return <div>foobar</div>
}
function Blub() {
return <Foo bar="bar value" baz={3} />
}
Lý do điều này có vấn đề là vì useEffect sẽ thực hiện kiểm tra tính bình đẳng tham chiếu trên options
giữa mọi lần hiển thị và nhờ cách hoạt động của JavaScript, options
sẽ luôn mới (vì options là object và tham chiếu là khác nhau giữa mỗi lần render), vì vậy khi React kiểm tra xem options
có thay đổi giữa các lần hiển thị hay không, nó sẽ luôn đánh giá thành true, nghĩa là i useEffect callback sẽ được gọi sau mỗi lần hiển thị thay vì chỉ khi bar
và baz
thay đổi.
Có hai điều chúng ta có thể làm để khắc phục điều này:
// option 1
function Foo({bar, baz}) {
React.useEffect(() => {
const options = {bar, baz}
buzz(options)
}, [bar, baz]) // we want this to re-run if bar or baz change
return <div>foobar</div>
}
Cách trên là cách tôi sẽ dùng khi tôi gặp trường hợp trên trong các project thực tế.
Nhưng có một tình huống và cách trên sẽ không dùng được: Nếu bar hoặc baz (không phải là primative) là các object / mảng / function / vv :
function Blub() {
const bar = () => {}
const baz = [1, 2, 3]
return <Foo bar={bar} baz={baz} />
}
Cách trên chỉ đúng khi biến dùng thuộc loại primitive (tìm hiểu primitive types and reference types)
Đây chính là lý do tại sao useCallback và useMemo tồn tại. Vì vậy, đây là cách bạn khắc phục điều này:
function Foo({bar, baz}) {
React.useEffect(() => {
const options = {bar, baz}
buzz(options)
}, [bar, baz])
return <div>foobar</div>
}
function Blub() {
const bar = React.useCallback(() => {}, [])
const baz = React.useMemo(() => [1, 2, 3], [])
return <Foo bar={bar} baz={baz} />
}
useCallback và useMemo dùng cho reference types
Posted on May 10, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.