Giới thiệu
Chào tất cả các bạn. Hôm nay mình sẽ cùng tìm hiểu về một chủ đề khá thú vị về React, đó chính là memo
, một khái niệm được React thêm vào từ version 16.6.
Trong React, việc re-render lại một component khi props và state thay đổi là cách để react update data mới cho component.
Tuy nhiên, khi xử lý với những component phức tạp, việc re-render nhiều lần sẽ trực tiếp ảnh hưởng tới hiệu suất của trang web.
Để tối ưu hiệu suất, cần phải tránh re-render những thứ không cần thiết. Các bạn cũng có thể tìm hiểu thêm về re-render trong link.
Dưới đây là một ví dụ
import { memo, useState } from 'react'; const Greeting = ({ name }) => { console.log("Greeting was rendered at", new Date().toLocaleTimeString()); return <h3>Hello{name && ', '}{name}!</h3>;
}; export default function MyApp() { const [name, setName] = useState(''); const [address, setAddress] = useState(''); return ( <> <label> Name{': '} <input value={name} onChange={e => setName(e.target.value)} /> </label> <label> Address{': '} <input value={address} onChange={e => setAddress(e.target.value)} /> </label> <Greeting name={name} /> </> );
}
Trong ví dụ này, bất cứ khi nào name
hoặc address
thay đổi thì toàn bộ MyApp
sẽ re-render khiến Greeting
là childComponent cũng re-render theo.
Mặc dù address
không liên quan tới Greeting
nhưng khi khí address
thay đổi thì Greeting
vẫn sẽ re-render theo.
Để tối ưu cho trường hợp này, ta phải hạn chế Greeting
re-render nhiều lần và sẽ chỉ re-render khi props của nó là name
thay đổi.
Lấy ý tưởng từ PureComponent và shouldComponentUpdate với mục đích skip render lại nếu props không thay đổi, memo
ra đời như một sự kế thừa khi có thể linh hoạt sử dụng trong functionComponent thay vì classComponent.
React.memo
memo lets you skip re-rendering a component when its props are unchanged.
Bây giờ với memo
ta có thể control việc re-render của Greeting
bằng việc wrapper nó.
Trong ví dụ này, memo
sẽ chỉ re-render nếu props của nó là name
thay đổi.
import { memo, useState } from 'react'; const Greeting = memo({ name }) => { console.log("Greeting was rendered at", new Date().toLocaleTimeString()); return <h3>Hello{name && ', '}{name}!</h3>;
}); export default function MyApp() { const [name, setName] = useState(''); const [address, setAddress] = useState(''); return ( <> <label> Name{': '} <input value={name} onChange={e => setName(e.target.value)} /> </label> <label> Address{': '} <input value={address} onChange={e => setAddress(e.target.value)} /> </label> <Greeting name={name} /> </> );
}
Shallow Compare
Tương tự với PureComponent và shouldComponentUpdate, để nhận biết được sự thay đổi của props, memo
cũng sử dụng so sánh Shallow Compare
.
Shallow Compare
hiểu đơn giản là so sánh nông sử dụng ===. Khi so sánh, ta cần đối chiếu giá trị và tham chiếu.
Với number, string, nó sẽ so sánh các giá trị của chúng. Tuy nhiên, khi so sánh các object, nó sẽ không so sánh các thuộc tính của chúng mà chỉ so sánh reference của chúng. Ví dụ
1 === 1 //true
{} === {} //false
[] === [] //false
{a:1} === {a:1} //false
Bản chất của let a = {}; và let b = {} là tham chiếu đến 2 địa chỉ khác nhau nên khi so sánh a === b
ta sẽ nhận được kết quả là false.
Việc này dẫn đến một số lỗi ta hay gặp phải khi mới tiếp cận với react và memo. Dưới đây là một ví dụ
import { memo, useState } from 'react'; const Greeting = memo(({ name }) => { console.log(name); return <h3>Hello {name.firstName} {name.lastName}!</h3>;
}); export default function MyApp() { const [name, setName] = useState({firstName: '', lastName: ''}); return ( <> <label> First Name{': '} <input value={name.firstName} onChange={e => setName({...name, firstName: e.target.value})} /> </label> <label> Last Name{': '} <input value={name.lastName} onChange={e => setName({...name, lastName: e.target.value})} /> </label> <button onClick={() => setName({firstName: '', lastName: ''})} >Clear all</button > <Greeting name={name} /> </> );
}
Trong trường hợp này, khi click vào button Clear all
nhiều lần, ta sẽ thấy Greeting
luôn re-render mặc dù giá trị name
luôn bằng {firstName: '', lastName: ''}
.
Khi gọi setName({firstName: '', lastName: ''})}
tức là đã tạo 1 tham chiếu mới và tham chiếu nó tới với name
vì vậy khi React.memo
so sánh sẽ trả về kết quả true và re-render lại Greeting
Để khắc phục việc này, ta thể làm như sau:
const initData = {firstName: '', lastName: ''}
const [name, setName] = useState(initData);
... <button onClick={() => setName(initData)} >Clear all</button > ....
Việc này giúp tham chiếu tới 1 giá trị initData nên React.memo
so sánh sẽ trả về kết quả false và không re-render.