React Hooksを使う上で意識したいことまとめ

# はじめに

Reactに関して最低限意識したいけどできてなかったなと思うところがあったので、
わかりやすいと感じた記事を参考に自分なりに噛み砕き、この１記事にまとめました。

# useStateの使い方に注意する

https://zenn.dev/mocomichi/articles/47c95425bc28d5

上記の記事がとてもわかりやすく、すごい学びになりました！
参考記事から学びになった部分を項目ごとにピックアップして書いていきます。

### 関連する状態はまとめることを検討する
よくあるパターンでは、ログイン情報などだと思っています。
例えば、ログインには`email`と`password`がセットでログインするのはあるあるですよね。
なので、以下のようになりますね！
↓書き換え前

```tsx
const [email, setEmail] = useState("")
const [password, setPassword] = useState("")

const handleChangeEmail = (value: stirng) => {
  setEmail(value)
}

const handleChangePassword = (value: string) => {
  setPassowrd(value)
}
```

↓書き換え後

```tsx
const [loginInfo, setLoginInfo] = useState({
  email: "",
  password: ""
})

const handleChangeEmail = (key: keyof typeof loginInfo, value: string) => {
  setLoginInfo(prev => ({
    ...prev,
    [key]: value,
  }))
}
```


### 矛盾した状態の宣言を避ける
これはコンポーネントの状態を増やしまくってしまったら意図せず起こりそうですね。
（自分が参画した地獄の案件でも矛盾した状態だらけでした。。）
参考記事では、`isSending`と`isSent` という、
送信中と送信後の場合は同時に`true`になり得ないので、
別々の状態で管理するのではなく、`status: “SENDING” | “SENT”` のような型の状態を
定義することで１つにまとまるという感じでした。

自分はモーダルでこのような`useState`を定義することが多いです。モーダルは同じタイミングで２つ以上開くことはないと思っているからです。
例えば以下のようになります。

↓書き換え前
```tsx
const [modalA, setModalA] = useState(false)
const [modalB, setModalB] = useState(false)

const toggleModalA = () => {
  setModalA(b => !b)
}

const toggleModalB = () => {
  setModalB(b => !b)
}
```

↓書き換え後

```tsx
const [selectedModal, setSelectedModal] = useState<"" | "modalA" | "modalB">("")

const toggleModalA = () => {
  setSelected(prev => prev !== "modalA" ? "modalA" : "")
}

const toggleModalB = () => {
  setSelected(prev => prev !== "modalB" ? "modalB" : "")
}
```

ちなみに、トグルのハンドラを以下のように書くこともできますが、
引数を受け取るのような関数にすると、
propsで受け渡す時に`() ⇒ toggleModal(”modalA”)`のように書くことになるので好きじゃないです。

```tsx
const toggleModal = (value: "modalA" | "modalB") => {
  setSelected(prev => prev !== value ? value : "")
}
```

### 冗長な使い方をしない
こちらは参考記事の内容では、`firstName`と`lastName`に加えて、
`fullName`のための状態を持ってしまっているのは良くないということでした。
```tsx
const [firstName, setFirstName] = useState("");
const [lastName, setLastName] = useState("");
const [fullName, setFullName] = useState("");

const handleChangeFirstName = (e) => {
  setFirstName(e.target.value);
  setFullName(e.target.value + ' ' + lastName); // ←これが勿体無い
}
const handleChangeLastName = (e) => {
  setLastName(e.target.value);
  setFullName(firstName + ' ' + e.target.value); // ←これが勿体無い
}
```
上記の記述は、`firstName`と`lastName` から`fullName` を計算できるにも関わらず、
状態として保持してしまうのはとても勿体無いし、
`firstName`や`lastName`の更新時と同時にわざわざ更新しないとなのも勿体無いですよね。

なので、以下のように修正できます。
```tsx
const [firstName, setFirstName] = useState("");
const [lastName, setLastName] = useState("");
fullName = firstName + " " + lastName;

const handleChangeFirstName = (e) => {
  setFirstName(e.target.value);
}
const handleChangeLastName = (e) => {
  setLastName(e.target.value);
}
```

もっというと、`firstName`と`lastName`は関連している情報なので、
`userName`のような一つの状態でまとめられるよねってことでした。

まとめると、既存の状態から計算できる値は状態で持つのではなく、
定数として定義して、レンダリングのたびに計算させるようにするということですね。

記事内でもう一つ紹介されていたのは、propsから渡ってきた値を状態で持つのも避けるべきということでした。
以下のような状態のことです。
```jsx
function Text({ children, color }) {
  const [textColor] = useState(color);

  return <h1 style={{ color: textColor }}>{children}</h1>;
}

export default function Example() {
  const [color, setColor] = useState("red");

  return (
    <div>
      <p>
        色を選択
        <select value={color} onChange={(e) => setColor(e.target.value)}>
          <option value="red">Red</option>
          <option value="blue">Blue</option>
          <option value="green">Green</option>
        </select>
      </p>
      <Text color={color}>色が変わります</Text>
    </div>
  );
}
```

これだと、レンダリングの際にのみuseStateが初期化されるため、
propsに変更が起きても`textColor`は変化せず、色が変わらないようになってしまいます。

なので、以下のように書き換えます。

```jsx
function Text({ children, color }) {
  const textColor = color;

  return <h1 style={{ color: textColor }}>{children}</h1>;
}
```
こうすると、propsが変更されるたびに`textColor`が変更されて色が正しく変わりますよね。
上記のような例が参考記事では紹介されていました。

自分的にはレンダリングに同期して値が変えたくないなどがない限りは、値を変更しないuseStateは必要ないと思います。

また、これらの例の通りuseStateを使わずに随時再計算をするような記述になると、
その計算が大きくなればなるほど、メモ化を検討するなどレンダリングを意識した実装を心がけるべきだと思っています。

### 重複した状態の宣言は避ける
早速、以下の記述を見ていただきます。

```jsx
const initialItems = [
  { id: 1, title: "taskA" },
  { id: 2, title: "taskB" },
  { id: 3, title: "taskC" },
];

const [tasks, setTasks] = useState(initialItems);
const [selectedTask, setSelectedTask] = useState(tasks[0]);

function handleTaskChange(taskId, e) {
  setTasks(tasks.map((task) => (task.id === taskId ? { ...task, title: e.target.value } : task)));
  setSelectedTask((task) => (task.id === taskId ? { ...task, title: e.target.value } : task));
}

function handleSelectedTaskChange(task) {
  setSelectedTask(task);
}
```

この記述のうち、`useState`として宣言されているものが`tasks`と`selectedTask`の二つとなっています。

この記述の何が問題かというと、
`task`と`selectedTask`という明らかに同じ内容のもののために`useState`を２つ定義していることです。
それによって、状態を更新する時にはタスクのリストに加え、
選択されたタスクも更新する必要があり、２度同じような更新の処理を書く必要があります。

つまるところ、ここが冗長だというわけですね。
こういった記述を改善したものが以下になります。

```jsx
const [tasks, setTasks] = useState(initialItems);
const [selectedTaskId, setSelectedTaskId] = useState(0);

function handleTaskChange(taskId, e) {
  setTasks(tasks.map((task) => (task.id === taskId ? { ...task, title: e.target.value } : task)));
}

function handleSelectedTaskIdChange(taskId) {
  setSelectedTaskId(taskId);
}

const selectedTask = tasks.find((task) => task.id === selectedTaskId);
```

要するに、選択されたタスクを状態として持つのではなく、
選択されたタスクの`id`を状態として持つことで、更新時に冗長になってしまう問題を解決できます。

そして選択されたタスクを定数としてレンダリング時に再計算する形で記述するといい感じになりました。
とてもわかりやすい例ですよね。元となる配列の情報から任意のものを１つ指定して扱いたい場合に、
`useState`を使って指定した情報そのものを新しく定義してしまいがちです。
しかしその場合は、指定した情報そのものではなく、
それを指すユニークな値を状態として持つと冗長な記述を回避できるよってことですね。

# useCallbackをとにかく使う

https://blog.uhy.ooo/entry/2021-02-23/usecallback-custom-hooks/

上記の記事、すごく確かに！って思いました。
`React.memo`だとか`useMemo`だとか`useCallback`だとか、よく聞くし、
パフォーマンス最適化というめちゃくちゃかっこよくて使いたい！！ってなりますよね笑

ただ実際相当のことがないと使わないと思いますし、
そもそもこれらのフックの呼び出しにオーバーヘッドがあり、
アプリケーションのパフォーマンスの低下を観測してからパフォーマンスチューニングしながら使うものだと思っています。

本題に入る前に、一旦`React.memo`、`useMemo`、`useCallback`の３つのついて、以下の記事等を参考にしていただければと思います。

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

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

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

https://zenn.dev/nus3/articles/1978a344cfaa4d3359c1

超簡単にこの記事内でも説明しておきます。

`React.memo`：
親→子コンポーネントに渡されているpropsに変更がない限りは、親コンポーネントのレンダリング時に子コンポーネントを再レンダリングしないようにできる。`React.memo`を使っていない場合は親コンポーネントと一緒にレンダリングされる。

`useMemo`：
関数の計算結果をメモ化する。依存配列に変更がない限り、同じ値を返し続ける。

`useCallback`：
関数自体をメモ化する。不要に関数インスタンスを作成するのを抑制し、前回の関数と同値（`a === b`の関係）の関数を返却するので、
`React.memo` を使用したコンポーネントに関数を受け渡す場合などに便利。
もし`useCallback` を使わなかった場合、関数オブジェクトなのでpropsに変更があったということになり、
`React.memo`を使用しなかった場合も再レンダリングされます。

`React.memo`、`useMemo`、`useCallback` に関して理解したところで、
「useCallbackをとにかく使う」という主張の具体的な根拠についてまとめていきます。

そもそも「useCallbackをとにかく使う」とはどういったことかについてお話すると、カスタムフックを作る際に、なんでもかんでも`useCallback` で囲むのはありかどうかという論点について、囲むべきという主張です。

> カスタムフックを作る理由は、普通の関数を作る理由と全く同じであり、すなわち**責務の分離**とか**カプセル化**です。一度カスタムフックとして分離された以上、インターフェースの内側のことはカスタムフック内で完結すべきです。 カスタムフックを使う側はカスタムフックの内側のことを知るべきではなく、その逆も然りです。
> 

確かにと思いました。参考記事でも言及されているのですが、
使う場所によって「ここは`React.memo` を使っているから`useCallback` を使おう」だとか、
「ここは`useCallback`が必要ないし、オーバーヘッドが気になるから使わないでおこう」
のような使う側の都合に合わせていたら、
再利用性や独立性に欠けるものコンポーネントと化してしまいます。

となると、果たしてカスタムフックとしての責務を担えているのかという問題になります。

それに、そもそも`useCallback`を使用することのオーバーヘッドってそんな辛いんだっけってことから考えても、最初から`useCallback`を使用するようにしておくべきということです。

> Reactでは値が同じであることにロジック上の意味が与えられるので、同じ意味の関数を返すときは極力オブジェクトとして同じ関数を返すべき
> 

こちらに関してもとても納得しました。

そもそも同値かそうでないかは、React自体に大きな意味を持つものです。なので、同値かそうでないを明確化することはロジック上も大きな意味を持つものであるということですね！

これらの理由から、自分も「useCallbackをとにかく使う」という結論に至りました。

# useEffectはなるべく使わない

https://beta.reactjs.org/learn/you-might-not-need-an-effect

この主張に関しては、公式docsにおける上記を元に言及しています。

どんな時に`useEffect`は必要ないのかについて説明していきます。
まずは、データの変更に伴って別のデータの変更を行う場合に`useEffect`が必要ないパターンです。
以下、参考記事の例です。

↓書き換え前

```jsx
function Form() {
  const [firstName, setFirstName] = useState('Taylor');
  const [lastName, setLastName] = useState('Swift');

  // 🔴 Avoid: redundant state and unnecessary Effect
  const [fullName, setFullName] = useState('');
  useEffect(() => {
    setFullName(firstName + ' ' + lastName);
  }, [firstName, lastName]);
  // ...
}
```

↓書き換え後

```jsx
function Form() {
  const [firstName, setFirstName] = useState('Taylor');
  const [lastName, setLastName] = useState('Swift');
  // ✅ Good: calculated during rendering
  const fullName = firstName + ' ' + lastName;
  // ...
}
```

解説すると、まず書き換え前は`firstName`または`lastName`に変更があった場合に`useEffect`を使用して`fullName`の状態を更新するといった処理を記述しています。

確かに`fullName`の値を正しく更新できてはいますが、非効率ですよね。
なぜなら書き換え後のように、レンダリング時に定数として計算した値を格納すれば問題ないからです。
これは「useStateの使い方に注意する」の部分でも記述したものと同じように、`useState`の値に変更が起こるタイミングで再計算が起こります。

なので、値の計算によって取得できる値を状態に持つことは余分であり、ましてや`useEffect`を使うのは明らかに不必要で、むしろ場合によっては無駄にレンダリングを起こしてしまう原因になります。
では、もしその再計算が重い処理であった場合はどうすべきかということを考えます。

以下のような処理があったとします。

```jsx
function TodoList({ todos, filter }) {
  const [newTodo, setNewTodo] = useState('');

  // 🔴 Avoid: redundant state and unnecessary Effect
  const [visibleTodos, setVisibleTodos] = useState([]);
  useEffect(() => {
    setVisibleTodos(getFilteredTodos(todos, filter));
  }, [todos, filter]);

  // ...
}
```

一旦`getFilteredTodos`が軽い処理であったと仮定して記述を見てみると、先述した通り、`useEffect`が不要なので以下のように書き換えられます。

```jsx
function TodoList({ todos, filter }) {
  const [newTodo, setNewTodo] = useState('');
  // ✅ This is fine if getFilteredTodos() is not slow.
  const visibleTodos = getFilteredTodos(todos, filter);
  // ...
}
```

これまでと同様、レンダリング時に再計算をする形で十分ですよね。
それでは、`getFilteredTodos`が重い場合はどう書くのかというと、以下のようになります。

```jsx
import { useMemo, useState } from 'react';

function TodoList({ todos, filter }) {
  const [newTodo, setNewTodo] = useState('');
  const visibleTodos = useMemo(() => {
    // ✅ Does not re-run unless todos or filter change
    return getFilteredTodos(todos, filter);
  }, [todos, filter]);
  // ...
}
```

上記で記述したとおり、メモ化を活用していくと依存配列の値（ここでは`todos`と`filter`）に変更がない限り再計算をせず、前回の値を使いまわしてくれます。

なので、もし重たい処理を何度も再計算してパフォーマンスが低下したとしても、`useMemo`を活用することで解決することができます。（メモ化については「useCallbackをとにかく使う」でも軽く触れてます）
次の`useEffect`が必要ないパターンは、propsの変更によって状態をリセットするパターンです。

まずは以下の記述をご覧ください。

```jsx
export default function ProfilePage({ userId }) {
  const [comment, setComment] = useState('');

  // 🔴 Avoid: Resetting state on prop change in an Effect
  useEffect(() => {
    setComment('');
  }, [userId]);
  // ...
}
```

こちらはユーザーごとのプロフィールを`userId`のpropsでユーザーを判別しています。
また`comment`という値によって、その`userId`に対応するユーザーのプロフィールにコメントができるというロジックが書いてあったとします。

その場合、`userId`が別のものになった時に前回の`userId`に対応するユーザーのプロフィールで入力中で`comment`の値に格納されている値はリセットされないといけないです。

そのため、`useEffect`によって`userId`に変更があった場合に`comment`の値を空の文字列にしてリセットしています。
もしリセットするための`useEffect`がなかった場合、Reactの同じコンポーネントかつ同じ場所にレンダリングされたコンポーネントの状態は保持し続けるので、`comment`の値が前回ユーザーのプロフィールで入力されていたものが残ってしまいます。

しかしこの場合もuseEffectを使用せずに対応することができます。

そのためには、以下のように書き換えます。

```jsx
export default function ProfilePage({ userId }) {
  return (
    <Profile
      userId={userId}
      key={userId}
    />
  );
}

function Profile({ userId }) {
  // ✅ This and any other state below will reset on key change automatically
  const [comment, setComment] = useState('');
  // ...
}
```

変わったところというと、状態を持っていた部分を切り離し、keyに`userId`を指定して対処しています。

これでなぜ対応できるのかというと、先ほど述べた「Reactの同じコンポーネントかつ同じ場所にレンダリングされたコンポーネントの状態は保持し続ける」という特徴をによるものです。

そもそも同じものとしてReactに認識されていたことが原因で状態がリセットされていなかったので、Reactにこれは違うものだということを教えてあげているのですね。

そのためには、keyにユニークなものを指定することでそういった対応が可能になります。
Reactでのkeyは、`map`などにも使うように特別な役割を果たしているのですね。
詳しくは以下の記事を見ていただけると、keyについて理解を深められるのではと思います。

https://progtext.net/programming/react-key/

一応、ここで簡単に説明しておくと、keyはReactがその項目と紐づけるためのものです。
keyはユニークな値でなければならず、その項目と１対１の関係になります。
要するにオブジェクトの`{ key: value }`のようなことです。
それでは一旦本題に戻ると上記のような場合にkeyを使うと`useEffect`を使用する必要がなくなることがわかりました。

では、もし２つ以上の状態のうち１つのみをリセットしたい場合はどうすべきでしょうか。
先ほどのやり方だと、全ての状態をリセットしてしまいます。
例によって、以下のような記述があるとします。

```jsx
function List({ items }) {
  const [isReverse, setIsReverse] = useState(false);
  const [selection, setSelection] = useState(null);

  // 🔴 Avoid: Adjusting state on prop change in an Effect
  useEffect(() => {
    setSelection(null);
  }, [items]);
  // ...
}
```

このようにitemsの変更に伴ってselectionの値をリセットする形を取ることができます。
しかしもちろんこれにも問題があります。どこが問題かというと、レンダリングが明らかに余分に起きてしまいます。

具体的にどういったことかというと、`items`に変更ができた時に１回レンダリングされ、その後`useEffect`内の`setSelection(null)`で`selection`をリセットした場合にもう一度レンダリングされてしまいます。
なので、以下のような記述に書き換えてみます。

```jsx
function List({ items }) {
  const [isReverse, setIsReverse] = useState(false);
  const [selection, setSelection] = useState(null);

  // Better: Adjust the state while rendering
  const [prevItems, setPrevItems] = useState(items);
  if (items !== prevItems) {
    setPrevItems(items);
    setSelection(null);
  }
  // ...
}
```

前回の値を状態として持ち上記のように扱うことで、
先ほどのような`items`の変更につられて２度変更が起きるようなことは無くなりました。
ところが、これは可読性的に微妙だったりするので、もっといい方法がありそうです。

例えば以下のように変更してみればどうでしょうか？

```jsx
function List({ items }) {
  const [isReverse, setIsReverse] = useState(false);
  const [selectedId, setSelectedId] = useState(null);
  // ✅ Best: Calculate everything during rendering
  const selection = items.find(item => item.id === selectedId) ?? null;
  // ...
}
```

`id`を状態として持つことによって、レンダリングを選択されている要素を指す値が変わったタイミングのみに絞ることができます。


次に紹介したいのが、アプリケーションを初期化する場合に`useEffect`を使ってしまうパターンです。
例えば以下のような記述があるとします。

```jsx
function App() {
  // 🔴 Avoid: Effects with logic that should only ever run once
  useEffect(() => {
    loadDataFromLocalStorage();
    checkAuthToken();
  }, []);
  // ...
}
```

こちらは最初のレンダリング時のみにローカルストレージをロードし、トークンのチェックを行う関数を実行しています。
ただこの`useEffect`も削減できます。

以下のように書き換えることで実現できます。

```jsx
if (typeof window !== 'undefined') { // Check if we're running in the browser.
   // ✅ Only runs once per app load
  checkAuthToken();
  loadDataFromLocalStorage();
}

function App() {
  // ...
}
```

上記のようにコンポーネントの中に書くのではなく、外側に記述することでファイルを読み込む１度きりの実行にすることができ、わざわざ`useEffect`を書かなくてもうまく実行することができます。

しかもReact18だと、開発環境で`useEffect`が２回実行されてしまうので、それによる想定外の動作も防ぐことができます。

# まとめ

ここまでReact Hooksに関するとてもわかりやすい記事と共に自分が理解しやすいように噛み砕いて説明していますが、やはりそれぞれの元記事を一度読んでみるともっと理解が深まると思うので一度見てみて下さい！

# 参考記事

https://zenn.dev/mocomichi/articles/47c95425bc28d5

https://blog.uhy.ooo/entry/2021-02-23/usecallback-custom-hooks/

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

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

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

https://zenn.dev/nus3/articles/1978a344cfaa4d3359c1

https://beta.reactjs.org/learn/you-might-not-need-an-effect

https://progtext.net/programming/react-key/