5
0

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

Posted at

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

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

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

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

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

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

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

私は今まで断片的にしか知らなかったので、特に重要そうなものを抜粋してまとめてみました。

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

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

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

副作用をレンダリング外で実行する理由は、レンダリングをクリーンに保ち、予測可能にするためです。

料理をするときを想像してください。
まず料理を作って、お皿に盛り付けます(これがレンダリングです)。
その後で、使った道具を洗ったり、残り物を冷蔵庫に入れたりします(これが副作用です)。

このように、副作用はコンポーネントの主な仕事が終わった後に行うのが自然で、アプリケーションの動作を予測しやすくします。

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 から返される値を変更してはいけない理由は、Props や State を変更してはいけない理由と似ています。

たとえば、先述した悪い例の場合、useruseUser フックで取得した情報です。

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

そのため、Hook から返される値を変更するとやはりコンポーネントの動作が予測しにくくなり、バグが発生する可能性があります。

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が呼び出されることを期待しているからです。

例えば、レゴブロックを組み立てるときを想像してください。説明書では、常に同じ順序でブロックを積み上げていきますよね。しかし、し途中で順番を変えたり、ブロックを抜いたりすると、完成品が想定通りにならないかもしれません。

同じように、ReactはHookの呼び出し順序を覚えていて、それぞれのHookの状態を正しく管理しています。もし条件分岐やループの中でHookを呼び出すと、レンダリングごとにHookの順序が変わる可能性があり、Reactが正しく状態を管理できなくなってしまいます。

まとめ

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

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

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

そのため、React の「お約束」を守ることで、正しく動きやすいコードを書くことができます。

5
0
0

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
5
0