
React re-render khi nào
mở đầu
Mấy nay lướt mạng xã hội, mình thấy một quan điểm sai về React, đó là thay đổi props truyền vào thì React Component render lại. Quan điểm trên mạng thì mình kệ thôi 😁, nhưng tiện đây thì mình cũng note nhanh vài dòng về việc re-render của React Component.
khi nào thì component re-render
vài trường hợp khiến cho component re-render.
- trường hợp 1, cơ bản nhất, ai cũng biết. đó là khi thay đổi
statecủa component. trong ví dụ dưới đây, thì hàmsetCount01vàsetCount02trigger update state, khiến component re-render.
const Hello = () => {
const [, setCount01] = useState(0);
const [, setCount02] = useReducer(c => ++c, 0);
return <div />
}- trường hợp 2, khi component cha re-render.
const Parent = () => {
const [, setCount] = useState(0);
return (
<>
<button onClick={() => setCount(c => ++c)}>Re-render</button>
<Child />
</>
)
}- trường hợp 3, khi context thay đổi. cụ thể là khi dùng
useContexthayuseSyncExternalStore, nếu context value thay đổi, thì component cũng bị re-render.
const Hello = () => {
const ctxValue = useContext(ctx);
// useSyncExternalStore hơi dài nên bỏ qua nhé 😀
return <div />
}quan điểm sai ban đầu
- mọi người thường hiểu sai
thay đổi props truyền vào thì React Component render lạivì trong hầu hết các trường hợp thì nó đúng là như vậy 😁. tuy nhiên, bản chất của nó là sự kết hợp củatrường hợp 1vàtrường hợp 2đã nêu ở trên.
const Parent = () => {
const [count, setCount] = useState(0);
return (
<>
<button onClick={() => setCount(c => ++c)}>Re-render</button>
<Child count={count} />
</>
)
}trong ví dụ trên, chạy hàm
setCountthay đổi state khiển componentParentre-render (trường hợp 1). Do componentParentre-render nên component conChildre-render (trường hợp 2). Ở đây, việc re-render không liên quan gì đến props truyền vàoChild. Kể cả không truyền props gì vàoChildthìChildvẫn sẽ bị re-render.nếu bạn vẫn chưa tin, thì hãy xem thêm ví dụ dưới đây 😤.
const Parent = () => {
let count = 0;
return (
<>
<button onClick={() => c => ++c}>Re-render</button>
<Child count={count} />
</>
)
}- trong ví dụ này, bấm button sẽ thay đổi giá trị
count, từ đó thay đổi props truyền vàoChildnhưng chắc chắn làChildkhông re-render rồi. nguyên nhân đã nói ngay trên. bạn có thể tự kiểm chứng thêm.
tránh re-render component một cách không cần thiết
về cơ bản thì việc này không thực sự cần thiết lắm cho đến khi ứng dụng của bạn có hiện tượng giật lag. hoặc là bạn quá rảnh 🙃. bắt đầu với vi dụ cơ bản này nhé.
const Child01 = ({ count }) => {
return <div>{count}</div>
}
const Parent = () => {
const [count, setCount] = useState(0);
return (
<>
<button onClick={() => setCount(c => ++c)}>Re-render</button>
<Child01 count={count} />
<Child02 />
<Child03 />
</>
)
}sau đây là vài cách đơn giản để tránh re-render lại các component khi chúng không thay đổi.
- đưa state xuống component (thế giới gọi là
move state down). khai báo và sử dụng state ở đúng component mà nó cần value của state này. tránh việc re-render lại componentParentvà các component anh chịChild02vàChild03một cách không cần thiết.
/*
setCount -> re-render Parent -> re-render cả 3 Child
count chỉ dùng ở mỗi Child01 => chuyển count xuống Child01
khi đó, update count ở Child01 không ảnh hưởng Parent, Child02 và Child03
*/
const NewParent = () => {
return (
<>
<Child01 />
<Child02 />
<Child03 />
</>
)
}
const NewChild01 = () => {
const [count, setCount] = useState(0);
return <div>{count}</div>
}- coi component như 1 props thông thường (thế giới gọi là
component as props). với cách này thì khiParentcomponent re-render thì không ảnh hưởng tớiChild01, do nó là 1 prop thôi.
const Temp = ({ child }) => <>{child}</>
const NewParent = () => {
return (
<>
<Temp child={<Child01 />}></Temp>
<Child02 />
<Child03 />
</>
)
}
/* hay 1 cách viết khiến bạn đẹp trai hơn, bằng cách tận dụng 'children' prop */
const Temp = ({ children }) => <>{children}</>
const NewParent = () => {
return (
<>
<Temp><Child01 /></Temp>
<Child02 />
<Child03 />
</>
)
}- sử dụng
React.memobọc component kết hợp với memoize lại các non-primitive props bằnguseCallbackvàuseMemo. với cách này, khi componentParentre-render,React.memosẽ so sánh nông các props truyền vàoChild01và không render lạiChild01nếu chúng không thay đổi giá trị.
const NewChild01 = React.memo(({ count }) => {
return <div>{count}</div>
});
const Parent = () => {
const [count, setCount] = useState(0);
/*
lưu ý là nếu count là number (primitive value) thì không cần memoize, nhưng nếu là non-primitive value như
function, object hay array thì cần memoize lại bằng useCallback / useMemo.
*/
const memoizedCount = useMemo(() => count);
return (
<>
<button onClick={() => setCount(c => ++c)}>Re-render</button>
<Child01 count={memoizedCount} />
<Child02 />
<Child03 />
</>
)
}cố tình re-render component
trường hợp này mình hiếm khi thấy nhưng cũng có thể xảy ra. ngoài API forceUpdate ở Class component để re-render component thì mình dựa vào lý thuyết đã nêu ở phần trên thôi. cơ bản nhất là tạo ra 1 state vô tri và set lại state đó khi ta cố tình muốn render lại.
const Hello = () => {
const [, setState] = usetState(0);
const forceRerender = () => setState(c => ++c);
}hay ngắn gọn hơn thì dùng useReducer như này
const [, forceRerender] = useReducer(c => ++c, 0);đấy, có thể thôi. còn cách nào khác thì mình cũng nghĩ thêm được 🫤.
