ReactHooksについてまとめる（Performance Hooks useCallback）

# 概要
ReactHooksのPerformance Hooksについてのメモ記事です。基本的にReactの公式ドキュメントの咀嚼記事となっています。

https://qiita.com/ta1m1kam/private/02be11aa1ee23d5512e2

今回はuseCallback編です。

## useCallback
### コンポーネントの再レンダリングスキップ
レンダリングパフォーマンスを最適化する際に、子コンポーネントに渡す関数をキャッシュする必要がある場合があります。まず、キャッシュを行うためにコンポーネントの関数をuseCallbackでラップします。

```jsx
import { useCallback } from 'react';

function ProductPage({ productId, referrer, theme }) {
  const handleSubmit = useCallback((orderDetails) => {
    post('/product/' + productId + '/buy', {
      referrer,
      orderDetails,
    });
  }, [productId, referrer]);
  // ...
```

useCallbackには引数が2つあります。
1つ目の引数には、再レンダリングの際にキャッシュしたい関数定義。2つ目は関数内で使用されるコンポーネント内の依存変数の配列です。
再レンダリング時に、依存配列の値を比較して変更されてない場合は以前と同じ関数を返し、変更されている場合は更新された関数を返します。


これは子コンポーネントの再レンダリングの際に効果を発揮します。例えば以下のような`ShippingForm`コンポーネントがあり、関数を引数として受け取ります。このコンポーネントはmemo化されています。

```ShippingForm.jsx
import { memo } from 'react';

const ShippingForm = memo(function ShippingForm({ onSubmit }) {
  // ...
});
```

この`ShippingForm`コンポーネントが親コンポーネントで使用され、propsとして、上記の例で使用したuseCallbackでラップされた`handleSubmit`関数を取るようにすると再レンダリングの際に依存するprops（productId, referrer）に変更がない限り再レンダリングをスキップするように動作します。
```ProductPage.jsx
function ProductPage({ productId, referrer, theme }) {
  // ...
  return (
    <div className={theme}>
      <ShippingForm onSubmit={handleSubmit} />
    </div>
  );
```


## メモ化されたコールバックからStateを更新する

メモ化されたコールバックの関数内でStateを更新する必要がある場合に付いてです。

handleAddTo関数で新しいtodoを追加しています。
```jsx
function TodoList() {
  const [todos, setTodos] = useState([]);

  const handleAddTodo = useCallback((text) => {
    const newTodo = { id: nextId++, text };
    setTodos([...todos, newTodo]);
  }, [todos]);
  // ...
```

こういう場合にはtodosの依存関係は不要です。

```jsx
function TodoList() {
  const [todos, setTodos] = useState([]);

  const handleAddTodo = useCallback((text) => {
    const newTodo = { id: nextId++, text };
    setTodos(todos => [...todos, newTodo]);
  }, []); // ✅ No need for the todos dependency
  // ...
```


## Effectが頻繁に発火しないようにする

useEffect内で関数を呼び出したい場合があります。

```jsx
function ChatRoom({ roomId }) {
  const [message, setMessage] = useState('');

  function createOptions() {
    return {
      serverUrl: 'https://localhost:1234',
      roomId: roomId
    };
  }

  useEffect(() => {
    const options = createOptions(); // ← This function
    const connection = createConnection();
    connection.connect();
    connection.connect();
    return () => connection.disconnect();
  }, [createOptions]); // 🔴 Problem: This dependency changes on every render
```
この場合、createOptions関数を依存配列に入れてしまうと、毎回Effectが発火してしまいます。
これを解決するには、呼び出されている `createOptions()`をuseCallbackでラップすればよいです。

```jsx
function ChatRoom({ roomId }) {
  const [message, setMessage] = useState('');

  const createOptions = useCallback(() => {
    return {
      serverUrl: 'https://localhost:1234',
      roomId: roomId
    };
  }, [roomId]); // ✅ Only changes when roomId changes

  useEffect(() => {
    const options = createOptions();
    const connection = createConnection();
    connection.connect();
    return () => connection.disconnect();
  }, [createOptions]); // ✅ Only changes when createOptions changes
  // ...
```

これでChatRoomコンポーネントのpropsである`roomId`が変更されない限りスキップされます。
以下のように関数自体をuseEffect内に入れてしまえば、依存関係が1つ減らすことができます。

こうすることで、useCallback自体が不要となることもあります。
```jsx
function ChatRoom({ roomId }) {
  const [message, setMessage] = useState('');

  useEffect(() => {
    function createOptions() { // ✅ No need for useCallback or function dependencies!
      return {
        serverUrl: 'https://localhost:1234',
        roomId: roomId
      };
    }

    const options = createOptions();
    const connection = createConnection();
    connection.connect();
    return () => connection.disconnect();
  }, [roomId]); // ✅ Only changes when roomId changes
  // ...
```


### カスタムHooksを最適化する

カスタムHooksを書く場合、その関数をuseCallbackでラップすることが推奨されています。
こうすることによって、必要な時に最適化された状態になります。

```jsx
function useRouter() {
  const { dispatch } = useContext(RouterStateContext);

  const navigate = useCallback((url) => {
    dispatch({ type: 'navigate', url });
  }, [dispatch]);

  const goBack = useCallback(() => {
    dispatch({ type: 'back' });
  }, [dispatch]);

  return {
    navigate,
    goBack,
  };
}
```

# 参考

https://beta.reactjs.org/apis/react/useCallback