Giới thiệu
Chào các bạn! Hôm nay, chúng ta sẽ khám phá một chủ đề rất thú vị trong React: memo
, một khái niệm mới được giới thiệu từ phiên bản 16.6.
Trong React, việc re-render một component khi props và state thay đổi là cơ chế chính giúp React cập nhật dữ liệu mới cho component. Tuy nhiên, khi làm việc với các component phức tạp, việc re-render liên tục có thể làm giảm hiệu suất của ứng dụng. Do đó, để tối ưu hóa hiệu suất, cần tránh việc re-render những component không cần thiết. Các bạn có thể tìm hiểu sâu hơn về vấn đề này qua liên kết bên dưới.
Ví dụ Minh Họa
jsx
import { useState } from 'react';
const Greeting = ({ name }) => {
console.log("Greeting được render lúc", new Date().toLocaleTimeString());
return <h3>Xin chào{name && ', '}{name}!</h3>;
};
export default function MyApp() {
const [name, setName] = useState('');
const [address, setAddress] = useState('');
return (
<>
<label>
Tên{': '}
<input value={name} onChange={e => setName(e.target.value)} />
</label>
<label>
Địa chỉ{': '}
<input value={address} onChange={e => setAddress(e.target.value)} />
</label>
<Greeting name={name} />
</>
);
}
Trong ví dụ trên, bất cứ khi nào name
hoặc address
thay đổi, toàn bộ MyApp
sẽ re-render và do đó, Greeting
cũng bị re-render. Mặc dù address
không liên quan đến Greeting
, nhưng sự thay đổi của nó vẫn khiến Greeting
được render lại.
Để tối ưu hóa trường hợp này, chúng ta cần ngăn chặn Greeting
re-render khi props của nó không thay đổi, chỉ khi name
thay đổi.
React.memo
Tại sao nên sử dụng memo
?
memo
cho phép bạn bỏ qua việc re-render một component khi props không thay đổi.
Giờ đây, với memo
, ta có thể kiểm soát việc re-render của Greeting
bằng cách bọc component này.
jsx
import { memo, useState } from 'react';
const Greeting = memo(({ name }) => {
console.log("Greeting được render lúc", new Date().toLocaleTimeString());
return <h3>Xin chào{name && ', '}{name}!</h3>;
});
export default function MyApp() {
const [name, setName] = useState('');
const [address, setAddress] = useState('');
return (
<>
<label>
Tên{': '}
<input value={name} onChange={e => setName(e.target.value)} />
</label>
<label>
Địa chỉ{': '}
<input value={address} onChange={e => setAddress(e.target.value)} />
</label>
<Greeting name={name} />
</>
);
}
Trong ví dụ này, memo
sẽ chỉ re-render Greeting
khi props là name
có sự thay đổi.
Shallow Compare
Cũng giống như PureComponent
và shouldComponentUpdate
, để phát hiện sự thay đổi của props, memo
sử dụng phương pháp so sánh nông (Shallow Compare).
Shallow Compare hoạt động như thế nào?
Shallow Compare thực chất là so sánh nông thông qua ===
. Đối với số, chuỗi, nó so sánh giá trị của chúng. Còn khi so sánh các đối tượng, nó chỉ so sánh địa chỉ tham chiếu, không so sánh thuộc tính bên trong. Ví dụ:
javascript
1 === 1 // true
{} === {} // false
[] === [] // false
{a:1} === {a:1} // false
Vấn đề xảy ra khi chúng ta có một trạng thái là đối tượng. Nếu chúng ta tạo một đối tượng mới mỗi khi cập nhật state, thì React.memo
sẽ phát hiện có sự thay đổi và re-render lại component, ngay cả khi nội dung không thay đổi.
Một Ví Dụ Cụ Thể
jsx
import { memo, useState } from 'react';
const Greeting = memo(({ name }) => {
console.log(name);
return <h3>Xin chào {name.firstName} {name.lastName}!</h3>;
});
export default function MyApp() {
const [name, setName] = useState({firstName: '', lastName: ''});
return (
<>
<label>
Tên{': '}
<input value={name.firstName} onChange={e => setName({...name, firstName: e.target.value})} />
</label>
<label>
Họ{': '}
<input value={name.lastName} onChange={e => setName({...name, lastName: e.target.value})} />
</label>
<button onClick={() => setName({firstName: '', lastName: ''})} >Xóa tất cả</button>
<Greeting name={name} />
</>
);
}
Trong trường hợp này, mỗi khi nhấn nút Xóa tất cả
, Greeting
vẫn sẽ bị re-render dù giá trị cuối cùng là {firstName: '', lastName: ''}
vì mỗi lần setName
đều tạo ra một tham chiếu mới.
Giải pháp khắc phục
Để khắc phục điều này, bạn có thể làm như sau:
jsx
const initData = {firstName: '', lastName: ''};
const [name, setName] = useState(initData);
...
<button onClick={() => setName(initData)} >Xóa tất cả</button>
...
Bằng cách này, bạn sử dụng chung một tham chiếu tới initData
, giúp React.memo
so sánh và nhận ra rằng không cần phải re-render Greeting
.
Kết Luận
Việc sử dụng React.memo
và hiểu về Shallow Compare là rất quan trọng trong việc tối ưu ứng dụng React của bạn. Hi vọng bài viết này đã cung cấp cho bạn những kiến thức bổ ích!
Tài Liệu Tham Khảo
- React.memo Documentation
- Hướng dẫn về Re-render trong React
source: viblo