13

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àm setCount01setCount02 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 hay useSyncExternalStore, 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ủa trường hợp 1trườ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 component Parent re-render (trường hợp 1). Do component Parent re-render nên component con Child re-render (trường hợp 2). Ở đây, việc re-render không liên quan gì đến props truyền vào Child. Kể cả không truyền props gì vào Child 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ào Child 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 component Parent và các component anh chị Child02Child03 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ì khi Parent component re-render thì không ảnh hưởng tới Child01, 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ằng useCallbackuseMemo. với cách này, khi component Parent re-render, React.memo sẽ so sánh nông các props truyền vào Child01 và không render lại Child01 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 forceUpdateClass 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 🫤.

tham khảo

mời bình luận 🗣️

bình luận 😨

chờ chút, đang tải bình luận...