【React】検索ボックス実装時のパフォーマンス改善案②

検索ボックスを実装したら画面が重たくなった…
という方向け
その②です

その①
https://qiita.com/nakakobiz0281/items/6acecb0e29824b463fb4

### 原因：onChangeのたびに不必要な画面レンダリングがされている

処理が重くなりがちな例
```example.js
export function Example() {
const [nameList, setNameList] = useState(nameListData)
// ↓画面にレンダリングするのに高負荷がかかる長さの配列
const [veryLongList, setVeryLongList] = ([])
const filteringName = (inputValue) => {
    if (inputValue) {
      const filteredNames = nameListData.filter((name) =>
        name.toLowerCase().includes(inputValue.toLowerCase())
      );
      setNameList(filteredNames);
    } else {
      setNameList(nameListData);
    }
};
...
    // 左側のリスト
    <Box sx={{ height: "50vh", width: "300px", padding: "15px" }}>
        <TextField
          margin="normal"
          fullWidth
          onChange={(e) => setInputText(e.currentTarget.value)}
        />
        <Box sx={{ height: "80%", overflow: "auto" }}>
          <List>
            {nameList.length > 0 ? (
              nameList.map((name) => (
                <ListItem key={name}>
                  <ListItemAvatar>
                    <Avatar>{name.substr(0, 1)}</Avatar>
                  </ListItemAvatar>
                  <ListItemText>{name}</ListItemText>
                </ListItem>
              ))
            ) : (
              <ListItem>
                <ListItemText>検索結果が見つかりませんでした</ListItemText>
              </ListItem>
            )}
          </List>
        </Box>
      </Box>
      // 右側のリスト
      <Box sx={{ height: "50vh", width: "300px", padding: "15px" }}>
        <TextField
          margin="normal"
          fullWidth
          onChange={(e) => setAnotherFunction(e.currentTarget.value)}
          placeholder="左の処理と全く関係ないリスト"
        />
        <Box sx={{ height: "80%", overflow: "auto" }}>
          <List>
          // ↓表示させる内容は変わらないのに,
          // veryLongListが毎回レンダリングされる
            {veryLongList.length > 0 ? (
              veryLongList.map((name) => (
                <ListItem key={name}>
                  <ListItemAvatar>
                    <Avatar>{name.substr(0, 1)}</Avatar>
                  </ListItemAvatar>
                  <ListItemText>{name}</ListItemText>
                </ListItem>
              ))
            ) : (
              <ListItem>
                <ListItemText>検索結果が見つかりませんでした</ListItemText>
              </ListItem>
            )}
          </List>
        </Box>
      </Box>

}
```
不要なレンダリングがされている参考動画
![不要なレンダリング.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3674898/644c455b-94f4-60c5-fac8-25f0f427a1d7.gif)

左側のテキストフィールドに文字を入力するたびに、右側のテキストフィールドやリストの要素一つ一つがレンダリングされてしまっています。
これはuseStateで管理している値が更新されたとき、そのuseStateがあるコンポーネント関数が再度レンダリングされる特性があるためです。
（今回の例でいうとonChangeでsetInputTextするたびに*Example*が再レンダリングされる。）

したがって同じファイルに記述されてる右側のリストも毎回レンダリングされてしまっています。

このような状態だとリストの要素が増えたり、他コンポーネントが追加されるたびに重たくなってしまいます。

### 解決策

**メモ化**して再レンダリングを防ぐ。
今回は**useMemo**を使ってみます。

```MemoizationList.js
export const MemoizationList = (data) => {
  return useMemo(() => {
    return (
      <Box sx={{ height: "50vh", width: "300px", padding: "15px" }}>
        <TextField
          margin="normal"
          fullWidth
          placeholder="左の処理と全く関係ないリスト"
        />
        <Box sx={{ height: "80%", overflow: "auto" }}>
          <List>
            {data.data.length > 0 ? (
              data.data.map((name) => (
                <ListItem key={name}>
                  <ListItemAvatar>
                    <Avatar>{name.substr(0, 1)}</Avatar>
                  </ListItemAvatar>
                  <ListItemText>{name}</ListItemText>
                </ListItem>
              ))
            ) : (
              <ListItem>
                <ListItemText>検索結果が見つかりませんでした</ListItemText>
              </ListItem>
            )}
          </List>
        </Box>
      </Box>
    );
  }, [data]);
};

```
手順
①メモ化したいリストをコンポーネント化する
②コンポーネントをuseMemoでラップする
③veryLongListの値が変わらない限り再レンダリングしないように、第二引数にveryLongListを設定する
④メモ化したものをreturnで返す

メモ化ができればあとは任意の場所に設置する
```example.js
const [nameList, setNameList] = useState(nameListData)
// ↓画面にレンダリングするのに高負荷がかかる長さの配列
const [veryLongList, setVeryLongList] = ([])
const filteringName = (inputValue) => {
    if (inputValue) {
      const filteredNames = nameListData.filter((name) =>
        name.toLowerCase().includes(inputValue.toLowerCase())
      );
      setNameList(filteredNames);
    } else {
      setNameList(nameListData);
    }
};
...
    // 左側のリスト
    <Box sx={{ height: "50vh", width: "300px", padding: "15px" }}>
        <TextField
          margin="normal"
          fullWidth
          onChange={(e) => setInputText(e.currentTarget.value)}
        />
        <Box sx={{ height: "80%", overflow: "auto" }}>
          <List>
            {nameList.length > 0 ? (
              nameList.map((name) => (
                <ListItem key={name}>
                  <ListItemAvatar>
                    <Avatar>{name.substr(0, 1)}</Avatar>
                  </ListItemAvatar>
                  <ListItemText>{name}</ListItemText>
                </ListItem>
              ))
            ) : (
              <ListItem>
                <ListItemText>検索結果が見つかりませんでした</ListItemText>
              </ListItem>
            )}
          </List>
        </Box>
      </Box>
      // メモ化した右側のリスト
      <MemoizationList  data={veryLongList} />

```

メモ化した後の実際の動き
![リストメモ化.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3674898/4c25755e-d327-a5d6-b71a-873e00ff56cc.gif)

右側のテキストフィールドやリストの要素がレンダリングされていません。
このようにメモ化することにより不必要な再レンダリングを防ぐことができます。

### 注意点

まずはどこの処理に一番時間がかかっているかを探すなどしてロジックを見直しましょう。
その上で画面レンダリングに多くの時間を使っているのであれば、メモ化を検討しましょう。

公式でも「あらゆる場所に useMemo を追加すべきか？」というTipsに「*useMemoを利用した最適化が力を発揮するのは、以下のような、ほんの一部のケースに限られます。…*」と書かれています。

**useMemo**の他にも**useCallback**などメモ化の方法はいくつかありますが、あくまでこれらは「無駄な再レンダリングを防ぐ」というものであり、アプリ全体の処理速度そのものを向上させるものではないことに注意しましょう。

#### 関連記事
https://qiita.com/nakakobiz0281/items/47fefcac8bf2c79cb022

https://qiita.com/nakakobiz0281/items/6acecb0e29824b463fb4

https://ja.react.dev/reference/react/useMemo