Rules of React を経典に React を書いて心の安寧を保つべし


こんにちは、とまだです。

React は Hook の効果もあり、書けばとりあえず動いてくれるので楽しいですよね！！！！！！！

ただ、**本当に正しいコードを書いているか不安になったことはありませんか？**

React は自由度が高い分、書き方によってはバグが発生しやすかったり、パフォーマンスが低下することもあります。

（その結果としてバグが発生し、しかも全く原因がわからない事態に何度も出会いました...）

そんなときに役に立つのが[「**Rules of React**」](https://react.dev/reference/rules)です。

これは、React 公式が提言している、React を使う時の「お約束」です。

今回はその中から、特に知っておきたいお約束を抜粋してまとめてみました。

:::note warn
直感的に理解してもらうことを大事にしているため、ニュアンスが正確ではない箇所もあるかもしれません。
あらかじめご了承ください。
:::

ちなみに Hooks の中身には詳しく触れていないので、Hooks に興味がある方は以下の記事がおすすめです。

https://qiita.com/tomada/items/d244ac3e0659b4b7c3f1

## 1. コンポーネントと Hook は「ピュア」であるべし！

https://react.dev/reference/rules/components-and-hooks-must-be-pure

### 「ピュア」とは？

Props と State は、React でデータを扱うための重要な概念です。

「ピュア」と言うと、純粋で清潔なイメージがありますよね。

一方 React で言う「ピュア」というのは、「きれいで、予測しやすい」という意味です。

例えば、自動販売機を想像してみてください。100 円入れたらいつも同じジュースが出てくるのが「ピュア」な状態です。

### 良い例と悪い例

では実際にコードを見てみましょう。

悪い例：

こちらのコンポーネントは、毎回違うジュースが出てくるので「ピュア」ではありません。

```jsx
function RandomJuice() {
  // 🙅‍♂️ ダメな例：毎回違うジュースが出てくる
  const juices = ['オレンジ', 'アップル', 'グレープ'];
  const random = Math.floor(Math.random() * juices.length);
  return <div>今日のジュース: {juices[random]}</div>;
}
```

また、時間によって結果が変わるのも「ピュア」ではありません。

```jsx
function TimeJuice() {
  // 🙅‍♂️ ダメな例：時間によって結果が変わる
  const now = new Date();
  return <div>今日のジュース: {now.getSeconds() % 3}</div>;
}
```

一方、以下のコンポーネントは、同じ味を指定したら、いつも同じ結果が返ってくるので「ピュア」です。

良い例：

```jsx
function Juice({ flavor }) {
  // 🙆‍♂️ いい例：同じ味を指定したら、いつも同じ結果
  return <div>今日のジュース: {flavor}</div>;
}
```

また、仮にランダムな値を使いたい場合は、コンポーネントの外で作るようにしましょう。

```jsx
// 先にランダムな値を作る
const juices = ['オレンジ', 'アップル', 'グレープ'];
const random = Math.floor(Math.random() * juices.length);

function RandomJuice({ flavor }) {
  // 作った値をコンポーネントに渡す
  return <Juice flavor={juices[random]} />;
}

// 引数として渡す
<Juice flavor={juices[random]} />;
```

### なぜピュアじゃないとダメ？

ピュアなコンポーネントを作ると、アプリの動きが予測しやすくなって、バグを見つけやすくなります。

逆にピュアじゃ無いコンポーネントの場合、予期せぬ動きをすることがあるので、バグを見つけるのが難しくなります。

その他にも、ピュアなコンポーネントは、再利用しやすく、テストしやすいというメリットがあります。

## 2. 副作用はレンダリング外で実行し、必要に応じてクリーンアップする

https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render

### 副作用とは？

副作用は、コンポーネントの主な仕事（レンダリング）以外のことです。

日常生活で例えると、料理をする時に、食材を買いに行くことが副作用です。料理をすることが主な仕事で、買い物はそのための準備ですよね。

React での副作用は、データの取得や更新、タイマーのセットなどがあります。

### 良い例と悪い例

以下の例は、コンポーネントの中に副作用がある悪い例です。

悪い例：

```jsx
function BirthdayCake() {
  // 🙅‍♂️ ダメな例：ケーキを焼きながらタイマーをセット
  setTimer(30);
  return <div>ケーキを焼いています...</div>;
}
```

このような場合、コンポーネントの中で副作用を行っているため、コンポーネントの動作が予測しにくくなります。

React の場合、副作用は `useEffect` フックを使って行います。`useEffect` フックは、レンダリング後に副作用を実行し、必要に応じてクリーンアップ関数を提供します。

良い例：

```jsx
function BirthdayCake() {
  useEffect(() => {
    // 🙆‍♂️ いい例：ケーキを焼き始めた後にタイマーをセット
    const timerId = setTimer(30);
    // クリーンアップ関数：コンポーネントがアンマウントされる時にタイマーをクリア
    return () => clearTimer(timerId);
  }, []);
  return <div>ケーキを焼いています...</div>;
}
```

### なぜレンダリング外で実行するの？

副作用をレンダリング外で実行する理由は、コンポーネントの主な仕事（レンダリング）をスムーズに行い、予測可能にするためです。

日常生活の例で考えてみましょう。

あなたがレストランのシェフ（コンポーネント）だとしましょう。
注文を受け、料理を作って提供するのがメインの仕事、つまりレンダリング（UI の作成）です。
提供が終わったら、お皿を洗ったり、次の準備をしたりするのが副作用です。

重要なのは、料理を作っている最中に皿洗いをしないことです。
同時にやると、料理が遅れたり、料理が冷めてしまったりするかもしれません。

同じように、React でも、副作用はレンダリングとは別のタイミングで行うことで、効率的に動作するようになるのです。

## 3. Props と State は「変えちゃダメ」

https://react.dev/reference/rules/components-and-hooks-must-be-pure#props-and-state-are-immutable

### Props と State とは？

Props と State は、React でデータを扱うための重要な概念です。

Props は、コンポーネントに渡されるデータのことです。

一方、State は、コンポーネント内で管理されるデータのことです。

### 良い例と悪い例

では、Props と State を変えてしまう悪い例を見てみましょう。

悪い例：

```jsx
function Counter({ count }) {
  // 🙅‍♂️ ダメな例：友達からもらったcount（プロップス）を勝手に変更
  count = count + 1;
  return <div>{count}</div>;
}
```

こちらは、Props で受け取った count を勝手に変更してしまっています。

では、Props と State を変えない良い例を見てみましょう。

良い例：

```jsx
function Counter({ count }) {
  // 🙆‍♂️ いい例：もらったcountはそのまま。新しい値を作る
  const newCount = count + 1;
  return <div>{newCount}</div>;
}
```

### なぜ Props と State を変えちゃダメ？

友達から借りた本に、勝手に書き込みをしたらダメですよね？
同じように、Props や State も「借りもの」だと思ってください。変更したいなら、新しいコピーを作るんです。

実際、たとえば、Props を変更してしまうと、親コンポーネントのデータも変わってしまうため、予期せぬバグが発生する可能性があります。

例：

```jsx
function Parent() {
  const [count, setCount] = useState(0);
  return <Child count={count} />;
}

function Child({ count }) {
  // 🙅‍♂️ ダメな例：親のcountを勝手に変更
  count = count + 1;
  return <div>{count}</div>;
}
```

この場合、Child コンポーネントで count を変更してしまうと、Parent コンポーネントの count も変わってしまいます。

言い換えると、コンポーネントの外にまで影響を及ぼすことがあるので、Props や State を変更することは避けるべきです。

## 4. Hook から返される値も「変えちゃダメ」

https://react.dev/reference/rules/components-and-hooks-must-be-pure#return-values-and-arguments-to-hooks-are-immutable

### Hook から返される値とは？

Hook は React の機能で、関数コンポーネントで状態などの React の機能を使えるようにします。

Hook から返される値は、コンポーネント内で使うデータや関数のことです。

例えば、以下のコードで `useUser` フックを使って、ユーザー情報を取得しています。

```jsx
function ProfilePage() {
  const user = useUser();
  return <div>{user.name}さん</div>;
}
```

### 良い例と悪い例

では、Hook から返される値を変えてしまう、悪い例を見てみましょう。

悪い例：

```jsx
function ProfilePage() {
  const user = useUser();
  // 🙅‍♂️ ダメな例：占いの結果（user情報）を勝手に変更
  user.lucky = true;
  return <div>{user.name}さんは今日ラッキー！</div>;
}
```

`user` は `useUser` フックで取得した情報です。

この情報はあくまで読み取り専用で、変更する前提で作られていません。

では、Hook から返される値を変えない「良い例」を見てみましょう。

良い例：

```jsx
function ProfilePage() {
  const user = useUser();
  // 🙆‍♂️ いい例：新しい情報を作る
  const luckyUser = { ...user, lucky: true };
  return <div>{luckyUser.name}さんは今日ラッキー！</div>;
}
```

このように、新しいオブジェクトを作成することで、Hook から返される値を変更せずに、新しい情報を作ることができます。

### なぜ Hook から返される値は「変えちゃダメ」？

Hook から返される値を変更してはいけない理由は、その情報が「読み取り専用」だからです。

日常生活の例で考えてみましょう。

図書館の本を借りたとき、以下のルールがありますよね。

1. 本の内容を読むことはできる（読み取り）
2. ただし、ページに勝手に書き込んではいけない（変更禁止）

同じように、Hook から返される値についても、読み取り専用であるべきなのです。

:::note warn
他の人（他のコンポーネント）も同じ本（情報）を使っているかもしれません。
勝手に変えると、他の人が困ってしまいますよね。

また、管理側である図書館（React）も、本は傷つけずに使ってほしいと思っているのです。
:::

繰り返しになりますが、何かを変更したい場合、元の情報を傷つけずに、新しいオブジェクトを作るようにしましょう。

本と同じく、自分で書き写したコピーをいじるのが一番安全です。

## 5. Hook はトップレベルでのみ呼び出す

https://react.dev/reference/rules/rules-of-hooks#only-call-hooks-at-the-top-level

### トップレベルとは？

コンポーネントの関数の一番外側のことを指します。具体的には、条件分岐やループ、ネストされた関数の中ではない部分です。

```jsx
function Counter() {
	// ここはトップレベル
	const [count, setCount] = useState(0);
	useEffect(() => {
		document.title = `You clicked ${count} times`;
	});

	if (count > 0) {
		// ここはトップレベルではない
	}

	for (let i = 0; i < count; i++) {
		// ここもトップレベルではない
	}
//...略
```

### 良い例と悪い例

では、トップレベルで Hook を呼び出す良い例と悪い例を見てみましょう。

良い例：

```jsx
function Counter() {
  // ✅ Good: トップレベルでHookを呼び出している
  const [count, setCount] = useState(0);
  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });

  // ...
}

// カスタムHook内でのHook呼び出しも可能
function useCustomHook() {
  // ✅ Good: カスタムHook内でのHook呼び出し
  const [value, setValue] = useState(0);
  // ...
  return value;
}
```

悪い例：

```jsx
function Counter() {
  if (count > 0) {
    // 🔴 Bad: 条件分岐の中でHookを呼び出している
    useEffect(() => {
      document.title = `Count: ${count}`;
    });
  }

  for (let i = 0; i < count; i++) {
    // 🔴 Bad: ループの中でHookを呼び出している
    const [item, setItem] = useState(null);
  }

  // ...
}
```

### なぜトップレベルで呼び出す必要があるの？

Hook をトップレベルで呼び出す必要がある理由は、React がコンポーネントの中で Hook が使われる順序を覚えているからです。

これも日常生活の例で説明してみましょう！

朝のルーティン：

1. 起床
2. 顔を洗う
3. 歯を磨く
4. 朝ごはんを食べる
5. 服を着る

このルーティンは、毎日同じ順序で行うと思います。

さて、このルーティンを条件付きで変えてしまったらどうなるでしょうか？

- 例：晴れなら、服を着てから朝ごはん
- 例：雨なら、朝ごはんを食べてから服を着る
- 例：寝坊したら、歯を磨かずに服を着る

:::note warn
これだと毎日の準備が不安定になり、何かを忘れたり間違えたりするかもしれませんね。
:::

React のコンポーネントも同じです。

React における Hook も、毎回同じ順序で呼び出すことが期待されています。

もちろん、異なる順序や条件で呼び出しても動く場合がありますが、タイミング次第で思わぬバグが発生する可能性があるので、トップレベルで呼び出すことを心がけましょう。

## まとめ

今回は、React の「お約束」である「Rules of React」について紹介しました。

1. コンポーネントと Hook は「ピュア」であるべし！
2. 副作用は「後片付け」のタイミングで実行する
3. Props と State は「変えちゃダメ」
4. Hook の結果も「変えちゃダメ」
5. Hook はトップレベルでのみ呼び出す

React は自由に書ける反面、油断するとバグが発生しやすかったり、予期せぬパフォーマンスの低下が起こりやすかったりします。

そのため、React の「お約束」を守ることで、正しく動きやすいコードを目指してみましょう。

## ちょっと宣伝

Qiita では「React を書ける人向け」記事を書いていますが、個人ブログでは「React を使いたい人向け」の記事も書いてます。
もし興味があれば覗いていただけると感謝感激です。

https://musclecoding.com/category/programming/react/

https://musclecoding.com/roadmap-frontend-engineer/