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
state
của component. trong ví dụ dưới đây, thì hàmsetCount01
vàsetCount02
trigger 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
useContext
hayuseSyncExternalStore
, 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ại
vì 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 1
và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
setCount
thay đổi state khiển componentParent
re-render (trường hợp 1
). Do componentParent
re-render nên component conChild
re-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àoChild
thìChild
vẫ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àoChild
nhưng chắc chắn làChild
khô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 componentParent
và các component anh chịChild02
vàChild03
mộ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ì khiParent
component 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.memo
bọc component kết hợp với memoize lại các non-primitive props bằnguseCallback
vàuseMemo
. với cách này, khi componentParent
re-render,React.memo
sẽ so sánh nông các props truyền vàoChild01
và không render lạiChild01
nế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 🫤.