Hãy sử dụng React.memo() một cách khôn ngoan
https://xdevclass.com/su-dung-react-memo-khon-ngoan/
Nội dung bài viết
Người dùng thích các trang web có UI nhanh và nhạy. Độ trễ phản hồi thấp dưới 100 mili giây tạo sẽ tạo cảm giác tức thì. Nếu độ trễ từ 100 – 300 mili giây sẽ làm người dùng cảm nhận rõ rệt về sự chậm chạp trang web.
Để cải thiện hiệu suất của web app, React cung cấp một HOC là React.memo()
. Khi React.memo()
bao quanh một component, React sẽ ghi nhớ kết quả render và bỏ qua các quá trình render không cần thiết.
Bài này mình sẽ giới thiệu về kĩ thuật memoization trong React, cụ thể là cách dùng React.memo()
để cải thiện performance, cũng như các trường hợp nên dùng và không cần dùng React.memo()
.
1. React.memo()
Trước khi quyết định cập nhật DOM thật, React sẽ render component, sau đó so sánh kết quả với lần render trước đó. Nếu cho ra kết quả khác nhau, React sẽ cập nhật lại DOM thật.
Công việc render ra VDOM và so sánh giữa 2 VDOM khá là nhanh. Nhưng bạn còn có thể tăng tốc web app bằng cách bỏ qua công đoạn so sánh này nếu chúng không cần thiết.
Khi một component được bao gói bởi React.memo()
, React render component và ghi nhớ kết quả. Trước khi đến lần render kế tiếp, nếu prop mới thì giống prop cũ, React sẽ sử dụng lại kết quả đã được ghi nhớ trước đó mà bỏ qua quá trình render.
Cùng xem cách hoạt động của việc ghi nhớ. Functional Component Movie
được bao gói bởi React.memo()
export function Movie({ title, releaseDate }) { return ( <div> <div>Movie title: {title}</div> <div>Release date: {releaseDate}</div> </div> );}export const MemoizedMovie = React.memo(Movie);
React.memo(Movie)
return một component được ghi nhớ mới có tên là MemoizedMovie
. Nó sẽ cho ra nội dung y hệt component Movie
nhưng có một khác biệt nho nhỏ.
Kết quả render của MemoizedMovie
được ghi nhớ. Nội dung ghi nhớ này sẽ được sử dụng lại miễn là title
và releaseDate
prop không thay đổi ở các lần render tiếp theo// Lần đầu, React gọi component MemoizedMovie và render<MemoizedMovie title="Heat" releaseDate="December 15, 1995"/>// Các lần tiếp theo, props vẫn không thay đổi// React sẽ gọi MemoizedMovie và lấy kết quả của lần render đầu// Không cần render lại nữa<MemoizedMovie title="Heat" releaseDate="December 15, 1995"/>
Mở console lên, bạn sẽ thấy React render <MemoizedMovie>
chỉ một lần, trong khi đó <Movie>
re-render lại rất nhiều lần.
Click vào đây nếu trình duyệt không hiển thị codesandbox
React.memo()
được thiết kế riêng cho functional component, tương tự như PureComponent
cho class component vậy
1.1 Custom việc so sánh các prop
Mặc định React.memo()
thực hiện so sánh nông (shallow compare) các prop.
Bạn có thể sử dụng tham số thứ 2 để quyết định việc so sánh thay cho việc so sánh mặc định của React.memo
React.memo(Component, [areEqual(prevProps, nextProps)]);
Hàm areEqual(prevProps, nextProps)
sẽ return true
nếu prevProps
và nextProps
bằng nhau
Ví dụ cách tính bằng nhau như thế nàyfunction moviePropsAreEqual(prevMovie, nextMovie) { return prevMovie.title === nextMovie.title && prevMovie.releaseDate === nextMovie.releaseDate;}const MemoizedMovie2 = React.memo(Movie, moviePropsAreEqual);
Hàm moviePropsAreEqual()
return true
nếu prevMovie
và nextMovie
bằng nhau.
2. Khi nào nên dùng React.memo()
Trước hết thì component của bạn phải là functional component đã nhé.
Nếu component của bạn luôn luôn bị re-render mặc dù prop không thay đổi
Component của bạn chứa một lượng lớn tính toán logic và UI như Chart, Canvas, 3D library…
Cá nhân mình sẽ dùng React.memo()
là mặc định luôn khi code React-Hook.
2.1 Component re-render thường xuyên với cùng prop giống nhau
Một trường hợp cực kì phổ biến khi code React như sau. MovieViewsRealtime
hiển thị số views
của một bộ phim, với dữ liệu cập nhật realtime.function MovieViewsRealtime({ title, releaseDate, views }) { return ( <div> <Movie title={title} releaseDate={releaseDate} /> Movie views: {views} </div> );}
App sẽ kết nối với server và cập nhật prop views
// Initial render<MovieViewsRealtime views={0} title="Forrest Gump" releaseDate="June 23, 1994"/>// After 1 second, views is 10<MovieViewsRealtime views={10} title="Forrest Gump" releaseDate="June 23, 1994"/>// After 2 seconds, views is 25<MovieViewsRealtime views={25} title="Forrest Gump" releaseDate="June 23, 1994"/>
Mỗi lần prop views
được cập nhật với một con số mới, MovieViewsRealtime
sẽ re-render. Điều này làm cho Movie
cũng re-reder theo mặc dù title
và releaseDate
không thay đổi.
Trường hợp này chúng ta sẽ áp dụng React.memo()
để hạn chế việc re-render trên Movie
componentfunction MemoizedMovie = React.memo(Movie)function MovieViewsRealtime({ title, releaseDate, views }) { return ( <div> <MemoizedMovie title={title} releaseDate={releaseDate} /> Movie views: {views} </div> )}
Miễn là title
và releaseDate
không thay đổi, React sẽ bỏ qua quá trình re-render MemoizedMovie
. Việc này sẽ giúp cải thiện hiệu suất của MovieViewsRealtime
component.
3. Khi nào tránh sử dụng React.memo()
Như mình đã nói ở trên thì mình sẽ dùng React.memo()
hầu hết mọi trường hợp khi sài React-Hook. Vậy những trường hợp nào bạn không nên sài React.memo()
Component của bạn là class component
Component của bạn đã được memo bởi một HOC khác, ví dụ connect() của Redux.
Có một số người cho rằng props component của họ thay đổi thường xuyên, nên họ không cần dùng React.memo()
. Cũng đúng, nhưng theo ý kiến cá nhân của mình khi một code trong một app React lớn, thì việc re-render component của bạn còn bị ảnh hưởng bởi các state của component cha, không chỉ là mỗi props các prop mà component con nhận vào. Nên chắc chắn sẽ có những lần re-render vô ích, không ít thì nhiều. Nên mình sẽ dùng React.memo()
luôn cho đảm bảo.
4. React.memo() và callback function
Các bạn chưa hiểu callback là gì thì có thể đọc bài này của mình nhá. Chinh phục High Order Function, Closures, Currying và Callback trong Javascript
Bây giờ chúng ta sẽ đi qua ví dụ:
Component Logout
nhận một callback prop là onLogout
, và được bao gói bởi React.memo()
:function Logout({ username, onLogout }) { return ( <div onClick={onLogout}> Logout {username} </div> );}const MemoizedLogout = React.memo(Logout);
Một component nhận một callback nên được xử lý với kĩ thuật memo. Mỗi lần component cha re-render nó sẽ tạo ra một instance callback mới.function MyApp({ store, cookies }) { return ( <div className="main"> <header> <MemoizedLogout username={store.username} onLogout={() => cookies.clear('session')} /> </header> {store.content} </div> );}
Lúc này mặc dù username
không thay đổi, MemoizedLogout
vẫn render lại vì nó nhận một instance mới của onLogout
callback.
React.memo()
lúc này không có tác dụng nữa rồi.
Để fix điều này, ta dùng một hook của React đó là useCallback() để ngăn việc tạo instance callback mới giữa mỗi lần render.const MemoizedLogout = React.memo(Logout);function MyApp({ store, cookies }) { const onLogout = useCallback( () => cookies.clear('session'), [cookies] ); return ( <div className="main"> <header> <MemoizedLogout username={store.username} onLogout={onLogout} /> </header> {store.content} </div> );}
useCallback(() => cookies.clear('session'), [cookies])
luôn luôn return một instance function, miễn là cookies
không thay đổi. Lúc này chúng ta đã fix được vấn đề trên.
5. Tóm lại
React.memo()
là một công cụ tuyệt vời để ghi nhớ functional component. Khi dùng chính xác nó sẽ giúp bạn tăng performance và UX lên đáng kể đấy. Hãy cảnh giác với callback function nữa nhé, luôn luôn đảm bảo rằng instance của callback luôn giống nhau giữa các lần re-render.
Bạn còn biết trường nào nên dùng React.memo() nữa không? Hãy comment phía dưới cho mình biết với nhé!
Tham khảo: https://dmitripavlutin.com/use-react-memo-wisely/
Last updated
Was this helpful?