5歳娘「パパのReact、めっちゃ遅いね！」

新しい記事もよろしくやで！
→[**ハスケル子「タグごとに色がついてたらいいのにな…」**](https://qiita.com/Yametaro/items/4f071b81c82741c6997a)

## 38歳無職ワイ

ワイ「（カタカタカタカタ・・・ッターン！）」

娘（**5歳**）「パパ、今日は何してるの？」

ワイ「今日はな、むかしWordPressで作った**自分用TODOリスト**の」
ワイ「デザインをリニューアルしてんねん」

よめ太郎「（そんなことより職を探せや）」

娘「へぇ〜」
娘「WordPressってことは、PHPを書いてるの？」

ワイ「いや、ちゃうで」
ワイ「リニューアル後は、フロント部分をReactで実装しようと思ってな」
ワイ「そこで、WordPressを**REST API**モードで使うことにしたんや」
ワイ「つまり、WordPressを**管理画面つきAPI**みたいに使うってことや」

娘「要は、WordPressを**ヘッドレスCMS**として使うんだね」

ワイ「ヘッドレス・・・？」
ワイ「ちゃうちゃう、**管理画面つきAPI**や」

娘「・・・まぁいいや」
娘「とにかく、APIからTODOリストのデータを取得して」
娘「それをReactで表示したいわけだね」

ワイ「そういうことや」

## 現在の進捗状況

娘「それで、フロントの実装はどこまでできたの？」

ワイ「ほぼ完成してるで」
ワイ「↓こんな感じや」

<img width="450" alt="スクショ" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/295452/fe3933f4-e636-7441-2081-ceddc1168041.png">

よめ太郎「（酒ばっかりやないかい・・・）」

ワイ「だいぶ下のほうに『職を探す』っていうタスクも」
ワイ「ちゃんと入ってるで！」

よめ太郎「（**ちゃんと**とは・・・）」

娘「へぇ〜」
娘「ちょっと触ってみてもいい？」

ワイ「もちろんええで」

娘「（ポチポチポチ・・・）」
娘「う〜ん・・・」

ワイ「え、どしたん」

#### 娘「パパのReact、めっちゃ遅いね！」

ワイ「え、マジ・・・？」

娘「なんか動きが重いよ？」

ワイ「え、そんなことないやろ・・・」
ワイ「（ポチポチポチ・・・）」
ワイ「あれ、ほんまや」
ワイ「検索フォームにタスク名を入力するときに、なんかモタついてんな」
ワイ「キーを叩いてから文字が表示されるまでの間に、タイムラグが**1秒近く**あるで」
ワイ「何やこれ・・・」

娘「パパ、もしかしたらコンポーネントたちが**無駄に再レンダー**されてるのかもよ」
娘「そんな時は、[React Developer Tools](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi?hl=ja)を使って**再レンダー状況を可視化**してみようよ」

ワイ「そ、そんな機能あんの？」

娘「あるよ」

1. Chromeに**React Developer Tools**という拡張機能を追加する
1. Chrome Devtoolsを開く
1. Componentsタブを開く
1. 歯車アイコンをクリック
1. `Highlight updates when components render.`にチェックを入れる

娘「手順としては↑こんな感じだね」

ワイ「なるほど・・・」

<img width="450" alt="スクショ" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/295452/0d5a0dcd-3b88-6aec-db58-21240a2801a1.png">

ワイ「↑ここにチェックを入れるんやな！」

娘「そうだね！」

## さっそくレンダー状況を見てみる

![1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/295452/7de49064-e6ca-a774-3460-9c65e348261e.gif)

ワイ「何やこれ」
ワイ「タスク名の入力フォームに**文字を1つ入力するたびに**」
ワイ「**100件**くらいある**TODOリスト全体が再レンダー**されてるやん」
ワイ「まだ**検索ボタンを押してもない**のに・・・！」

娘「そうだね・・・」

ワイ「検索ボタンを押して、そんで検索結果が変わったことでリストが再レンダーされるならまだしも」
ワイ「**文字を1つ入力したり消したりするたびに**リスト全体が再レンダーされるんかい」
ワイ「何やそれ・・・」

娘「たぶん、タスク名の`input`タグにイベントハンドラが設定されてて」
娘「文字を入力するたびに何かページの状態が変わって」
娘「それでページが再レンダーされてるんじゃない？」
娘「ページ内で`useState`とか使ってるよね？」

ワイ「おお、それはその通りや」

```diff_tsx:src/pages/TodoListPage.tsx
  const [taskName, setTaskName] = useState('')
```

ワイ「↑この、`useState`で作った`setTaskName`っていう関数があってな」
ワイ「その関数が、タスク名の入力フォームに文字を入力するたびに実行される仕組みやねん」

娘「なるほどね」
娘「その時に、TODOリストも毎回ぜんぶ再レンダーされちゃってるんだね」

## `React.memo`で、コンポーネントをメモ化しよう

娘「パパ、そんな時は`React.memo`を使って」
娘「TODOリストのコンポーネントを**メモ化**してみようよ」

ワイ「ど、どうやんの・・・？」

娘「ええと・・・」

```diff_tsx:src/components/TodoList.tsx
- const TodoList: React.FC<Props> = ({ list }) => {
+ const TodoList: React.FC<Props> = React.memo(({ list }) => {
    return (
      <ul>
        {list.map((task) => (
          <TodoItem task={task} key={task.id} />
        ))}
      </ul>
    )
- }
+ })
   
+ TodoList.displayName = 'TodoList'
```

娘「↑こうだね」

ワイ「おお、`React.memo`っていう関数に」
ワイ「`TodoList`コンポーネントの中身をぶち込んであげるだけなんやな」

娘「うん！」

よめ太郎「（前々から思ってたけど）」
よめ太郎「（引数を**ぶち込む**って何やねん・・・）」

## モタつきは解消されたのか

![2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/295452/53d0dbf1-6bea-4ff0-cebf-16cbde6ea92e.gif)

ワイ「おお、TODOリストがピカピカせんようになっとるわ」
ワイ「文字の入力が重たい感じもなくなっとる」

娘「よかったね！」

ワイ「・・・これはどういうことなん？」

## 娘ちゃんによる解説

娘「えっとね、今までは」

> React「お、検索フォームに文字が入力されたで！」
> React「ほな、ページの状態が変わったことやし、再レンダーや！」
> React「`TodoList`コンポーネントも再レンダーや！」

娘「↑こんな感じだったんだけど」
娘「コンポーネントをメモ化すると・・・」

> React「お、検索フォームに文字が入力されたで！」
> React「でも、TODOリストの中身は変わってないから」
> React「`TodoList`コンポーネントはレンダーし直す必要ないか・・・」
> React「さっきメモしといた結果を再利用しとこか」

娘「↑こんな感じだね」

よめ太郎「（なんでReactも関西弁やねん）」


ワイ「なるほどなぁ」
ワイ「`useState`で管理している状態が、文字入力によって変わったとしても」
ワイ「TODOリストに関係のない変更だけが起こっていた場合には」
ワイ「再レンダーをスキップしてくれるんやね」

娘「そういうことだね！」

ワイ「ありがとうやで、娘ちゃん！」

## しかし30分後・・・

ワイ「こ、今度はタスクの削除機能を実装したら」
ワイ「また画面の動きが遅くなってもうた・・・」

娘「パパ、今度はどうしたの？」

ワイ「おお、娘ちゃん・・・」
ワイ「あのな・・・」


```diff_tsx:src/pages/TodoListPage.tsx
+ const onDelete = (taskId: number): void => {
+   /* 省略 */
+ }

  return (
    <>
      <h1>TODOリスト</h1>
      <form>
        {/* 省略 */}
      </form>
-     <TodoList list={todoList} />
+     <TodoList list={todoList} onDelete={onDelete} />
    </>
  )
```

ワイ「↑こんな感じで、タスク削除用の関数を作って」
ワイ「それを`TodoList`コンポーネントに渡すようにしただけなんや・・・」
ワイ「それなのに・・・」

![3.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/295452/44a812e2-cbee-0c04-63c3-c1894b2db591.gif)

ワイ「また文字入力のたびにTODOリスト全体がピカピカしてんねん・・・」

娘「そんな時は、また**メモ化だね！**」

## `useCallback`で、関数もメモ化しよう

```diff_tsx:src/pages/TodoListPage.tsx
-  const onDelete = (taskId: number): void => {
+  const onDelete = useCallback((taskId: number): void => {
     setTodoList((prevList) => {
       return prevList.filter((todoItem) => todoItem.id !== taskId)
     })
-  }
+  }, [])
```

ワイ「おお、今度は`onDelete`関数の中身を」
ワイ「`useCallback`というやつにぶち込んだるわけか」

娘「そうそう」
娘「これでもう一度、画面を触ってみて？」

![4.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/295452/f756f209-5886-d64d-705d-d40f08ac72ec.gif)

ワイ「おお、チカチカしないし、サクサク入力できとるわ」
ワイ「・・・でも、これ何でなん？」
ワイ「`TodoList`コンポーネントに渡す`onDelete`関数は、毎回おんなじ関数なんやから」
ワイ「`TodoList`は再レンダーされないはずちゃうの・・・？」

娘「それはね・・・」

```diff_tsx:src/pages/TodoListPage.tsx
  const onDelete = (taskId: number): void => {
    /* 省略 */
  }

  return (
    <>
      <h1>TODOリスト</h1>
      <form>
        {/* 省略 */}
      </form>
      <TodoList list={todoList} onDelete={onDelete} />
    </>
  )
```

娘「↑ここで、`onDelete`関数を作って」
娘「`TodoList`コンポーネントに渡してるでしょ？」
娘「そこが問題・・・というかポイントなの」
娘「つまり・・・」

> React「よっしゃ、`onDelete`関数を作って」
> React「`TodoList`コンポーネントにぶち込むでぇ！」

娘「↑こんな感じで、**作りたての新しい関数**を`TodoList`コンポーネントに渡す」
娘「そういうことになっちゃうの」

ワイ「作りたて・・・」
ワイ「そう言われてみれば、アロー関数式で**関数を作って**」
ワイ「それを`TodoList`コンポーネントに渡してるわけやもんな」

娘「そう」
娘「だから・・・」

> React「さっきまでとは違う、今作ったばかりの`onDelete`関数を渡すから」
> React「`TodoList`コンポーネントも再レンダーや！」

娘「↑こんな感じになっちやうの」

ワイ「毎回新しい関数が作られて、**別物扱い**になってまうわけか」
ワイ「つまり、Reactは[**Object.is**](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/is)による比較アルゴリズムを使用しているわけやな・・・」

よめ太郎「（いや、そこは急に理解早いんかい）」

娘「そういうことだね」
娘「そして、`useCallback`を使うと」

> ワイ「この関数は毎回作り直さないで、前回と同じのを使ってな！」

娘「ってことをReactに伝えられるわけだね」

ワイ「ふーん、そうすると`TodoList`コンポーネントが再レンダーされなくなるん？」

娘「そうだよ」

> React「TODOリストの内容も`onDelete`関数も、前回と変わってへんな」
> React「ほな今回は`TodoList`コンポーネントの再レンダーはスキップや！」

娘「↑って感じになるの」

ワイ「なるほどなぁ」
ワイ「ちなみに`useCallback`の第二引数の配列、これは何なん？」

娘「これは・・・」

> ワイ「`hoge`っていう変数の中身が変わった場合には」
> ワイ「前回と同じ関数を使いまわさずに、新しく作ってな！」

娘「↑的なことをReactに伝えるときのための配列だね」

ワイ「ほぇ〜」
ワイ「そうやって新しく関数が作り直された場合には」
ワイ「その関数を受け取ったコンポーネントは再レンダーされるわけか」

娘「そうそう」

## ちなみに

娘「ちなみに、`useCallback`を使わないでも」
娘「**ページコンポーネントの外で関数を定義**するだけで」
娘「再レンダーを抑止できる場合もあるよ！」

ワイ「そうかそうか」
ワイ「ページコンポーネントの外側で定義された関数は」
ワイ「ページが再レンダーされても変わらへんわけやな」
ワイ「まぁ、とにかく激重サイトにならんくてよかったわ！」

## まとめ

- Reactアプリの動作が重い時は、**React Developer Tools**で再レンダー状況をチェックしてみよう
- `React.memo`を使ってコンポーネントをメモ化し、無駄な再レンダーを抑止しよう
- メモ化したコンポーネントに渡すための関数も`useCallback`でメモ化しておこう

ワイ「↑ってことやな！」

娘「そうだね！」

ワイ「いやー、娘ちゃんのおかげで良いTODOリストができそうやわ・・・！」

娘「あっ！」
娘「・・・パパ・・・！」

ワイ「なんや？」

娘「大変なことに気づいたんだけど・・・」

#### 娘「このTODOリスト、どこからタスクを追加するの？」

ワイ「あ・・・」
ワイ「タスクの作成機能を作るの、忘れてたわ・・・！」

娘「・・・」

ワイ「で、でも大丈夫や！」

> - [ ] タスク追加機能を実装する

ワイ「↑っていうタスクを、このTODOリストに追加しておけばええねん！」
ワイ「そしたらもう忘れへん！」

娘「で、そのタスクは**どこから追加するの？**」

ワイ「**それな**」

**〜おしまい〜**


## 参考文献
- [**React の最上位 API『React.memo』**](https://ja.reactjs.org/docs/react-api.html#reactmemo)
- [**差分検出処理 – React**](https://ja.reactjs.org/docs/reconciliation.html)
- [**Object.is() - JavaScript | MDN - Mozilla**](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/is)

## 新しい記事もよろしくやで！
[**ハスケル子「タグごとに色がついてたらいいのにな…」**](https://qiita.com/Yametaro/items/4f071b81c82741c6997a)

## 補足
https://twitter.com/yametaro1983/status/1383936228668497925?s=21