1. 程式人生 > 其它 >對 react hooks 的理解

對 react hooks 的理解

通常情況下,React 會有多餘的 render。

1. 常用的場景一:子元件依賴父元件資料,當父元件資料更新時,會重新渲染子元件。

// index.tsx
function Index() {
  const [count, setCount] = useState(0);

  const handleAdd = () => {
    setCount((i) => i + 1);
  };

  return (
    <div className="container">
      <h2>{count}</h2>
      <Button type="primary" size="large" onClick={handleAdd}>
        點選 +1
      </Button>
      <Child data={count} />
    </div>
  );
}

// child.tsx
function Child(props: any) {
  console.log("child 被渲染了!!");

  return (
    <div className="container">
      <Card>child 被渲染了({props.data})</Card>
    </div>
  );
}

2. 常見的場景二:子元件依賴父元件資料,當父元件其他資料(非子元件依賴的資料)更新時,子元件也會重新渲染!

// index.tsx
// const data = 0; // 定義一個常量
function Index() {
  const [count, setCount] = useState(0);
  const [data, setData] = useState(0);

  const handleAdd = () => {
    setCount((i) => i + 1);
  };

  return (
    <div className="container">
      <h2>{count}</h2>
      <Button type="primary" size="large" onClick={handleAdd}>
        點選 +1
      </Button>
      <Child data={data} />
    </div>
  );
}

// child.tsx
function Child(props: any) {
  console.log("child 被渲染了!!");

  return (
    <div className="container">
      <Card>child 被渲染了({props.data})</Card>
    </div>
  );
}

3. 常見的場景三:子元件不依賴父元件資料,當父元件資料更新時,子元件也會重新渲染!!

// index.tsx
function Index() {
  const [count, setCount] = useState(0);

  const handleAdd = () => {
    setCount((i) => i + 1);
  };

  return (
    <div className="container">
      <h2>{count}</h2>
      <Button type="primary" size="large" onClick={handleAdd}>
        點選 +1
      </Button>
      <ChildPure />
    </div>
  );
}

// child.tsx
function ChildPure() {
  console.log("childPure 被渲染了!!");

  return (
    <div className="container">
      <Card>childPure 被渲染了</Card>
    </div>
  );
}

【總結】不論子元件是否有依賴父元件的資料,當父元件資料更新時(也就是呼叫 setXXX()),react 會重新渲染整個檢視,其中包括了子元件。多數情況下,這種渲染元件操作是多餘的、浪費的,react.memo() 可以用來解決這個問題。

react.memo

將程式碼裡的 Child 用 React.memo(Child) 包裹,當 props 沒有更新時,React 會跳過渲染元件的操作並直接服用最近一次渲染的結果。

// index.tsx
function Index() {
  const [count, setCount] = useState(0);

  const handleAdd = () => {
    setCount((i) => i + 1);
  };

  return (
    <div className="container">
      <h2>{count}</h2>
      <Button type="primary" size="large" onClick={handleAdd}>
        點選 +1
      </Button>
      <ChildPureMemo />
    </div>
  );
}

// child.tsx
function ChildPure() {
  console.log("childPure 被渲染了!!");

  return (
    <div className="container">
      <Card>childPure 被渲染了</Card>
    </div>
  );
}

export const ChildPureMemo = React.memo(ChildPure);

但是這裡還有個bug,當給元件添加了監聽事件後,又回到了最初的重複渲染狀態。

// index.tsx
function Index() {
  const [count, setCount] = useState(0);

  const handleAdd = () => {
    setCount((i) => i + 1);
  };

  const handleData = () => {};

  return (
    <div className="container">
      <h2>{count}</h2>
      <Button type="primary" size="large" onClick={handleAdd}>
        點選 +1
      </Button>
      <ChildMemo data={data} onClick={handleData} />
    </div>
  );
}

// child.tsx
function Child(props: any) {
  console.log("child 被渲染了!!");

  return (
    <div className="container">
      <Card onClick={props.onClick}>child 被渲染了({props.data})</Card>
    </div>
  );
}

export const ChildMemo = React.memo(Child);

[總結] 使用 react.memo() 包裹的子元件,當 props 沒有變化時,不會重新渲染子元件。但是監聽事件在父元件重新渲染時,雖然功能一樣,但引用地址變化了!!所以對於子元件來說,props發生了變化,進行重新渲染。這種情況下,需要使用 useCallback 解決。

useCallback

useCallback 的第二個引數是依賴項,當依賴發生變化時,才會重新計算新的value,如果依賴不變,就重用之前的value。

// index.tsx
function Index() {
  const [count, setCount] = useState(0);

  const handleAdd = () => {
    setCount((i) => i + 1);
  };

  const handleData = useCallback(() => {
    console.log(111);
    setData((i) => i + 1);
  }, []);

  return (
    <div className="container">
      <h2>{count}</h2>
      <Button type="primary" size="large" onClick={handleAdd}>
        點選 +1
      </Button>
      <ChildMemo data={data} onClick={handleData} />
    </div>
  );
}

// child.tsx
function Child(props: any) {
  console.log("child 被渲染了!!");

  return (
    <div className="container">
      <Card onClick={props.onClick}>child 被渲染了({props.data})</Card>
    </div>
  );
}

export const ChildMemo = React.memo(Child);

useMemo

useMemo 的用途和 useCallback 一樣,只是第一個引數的型別不一樣。
useCallback(value, []) => useMemo(()=>value,[]) 如果 value 是一個函式時,useMemo 的寫法會相對不易理解

// index.tsx
function Index() {
  const [count, setCount] = useState(0);

  const handleAdd = () => {
    setCount((i) => i + 1);
  };

  const handleData = useMemo(
    () => () => {
      console.log(111);
      setData((i) => i + 1);
    },
    []
  );

  return (
    <div className="container">
      <h2>{count}</h2>
      <Button type="primary" size="large" onClick={handleAdd}>
        點選 +1
      </Button>
      <ChildMemo data={data} onClick={handleData} />
    </div>
  );
}

// child.tsx
function Child(props: any) {
  console.log("child 被渲染了!!");

  return (
    <div className="container">
      <Card onClick={props.onClick}>child 被渲染了({props.data})</Card>
    </div>
  );
}

export const ChildMemo = React.memo(Child);

官方文件

1. react.memo

2. useCallback

3. useMemo

參考文件

1. 淺談 Hooks