186
177

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Last updated at Posted at 2024-10-04

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

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

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

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

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

そんなときに役に立つのがRules of Reactです。

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

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

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

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

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

「ピュア」とは?

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

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

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

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

良い例と悪い例

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

悪い例:

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

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

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

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

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

良い例:

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

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

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

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

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

なぜピュアじゃないとダメ?

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

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

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

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

副作用とは?

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

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

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

良い例と悪い例

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

悪い例:

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

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

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

良い例:

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

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

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

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

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

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

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

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

PropsとStateとは?

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

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

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

良い例と悪い例

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

悪い例:

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

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

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

良い例:

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

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

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

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

例:

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から返される値も「変えちゃダメ」

Hookから返される値とは?

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

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

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

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

良い例と悪い例

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

悪い例:

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

useruseUser フックで取得した情報です。

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

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

良い例:

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

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

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

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

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

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

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

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

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

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

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

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

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

トップレベルとは?

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

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を呼び出す良い例と悪い例を見てみましょう。

良い例:

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;
}

悪い例:

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. 服を着る

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

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

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

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

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

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

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

まとめ

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

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

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

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

ちょっと宣伝

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

186
177
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
186
177

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?