こんにちは、とまだです。
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>;
}
user
は useUser
フックで取得した情報です。
この情報はあくまで読み取り専用で、変更する前提で作られていません。
では、Hook から返される値を変えない「良い例」を見てみましょう。
良い例:
function ProfilePage() {
const user = useUser();
// 🙆♂️ いい例:新しい情報を作る
const luckyUser = { ...user, lucky: true };
return <div>{luckyUser.name}さんは今日ラッキー!</div>;
}
このように、新しいオブジェクトを作成することで、Hook から返される値を変更せずに、新しい情報を作ることができます。
なぜHookから返される値は「変えちゃダメ」?
Hookから返される値を変更してはいけない理由は、その情報が「読み取り専用」だからです。
日常生活の例で考えてみましょう。
図書館の本を借りたとき、以下のルールがありますよね。
- 本の内容を読むことはできる(読み取り)
- ただし、ページに勝手に書き込んではいけない(変更禁止)
同じように、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が使われる順序を覚えているからです。
これも日常生活の例で説明してみましょう!
朝のルーティン:
- 起床
- 顔を洗う
- 歯を磨く
- 朝ごはんを食べる
- 服を着る
このルーティンは、毎日同じ順序で行うと思います。
さて、このルーティンを条件付きで変えてしまったらどうなるでしょうか?
- 例:晴れなら、服を着てから朝ごはん
- 例:雨なら、朝ごはんを食べてから服を着る
- 例:寝坊したら、歯を磨かずに服を着る
これだと毎日の準備が不安定になり、何かを忘れたり間違えたりするかもしれませんね。
Reactのコンポーネントも同じです。
React における Hook も、毎回同じ順序で呼び出すことが期待されています。
もちろん、異なる順序や条件で呼び出しても動く場合がありますが、タイミング次第で思わぬバグが発生する可能性があるので、トップレベルで呼び出すことを心がけましょう。
まとめ
今回は、React の「お約束」である「Rules of React」について紹介しました。
- コンポーネントとHookは「ピュア」であるべし!
- 副作用は「後片付け」のタイミングで実行する
- PropsとStateは「変えちゃダメ」
- Hookの結果も「変えちゃダメ」
- Hookはトップレベルでのみ呼び出す
React は自由に書ける反面、油断するとバグが発生しやすかったり、予期せぬパフォーマンスの低下が起こりやすかったりします。
そのため、React の「お約束」を守ることで、正しく動きやすいコードを目指してみましょう。
ちょっと宣伝
Qiita では「React を書ける人向け」記事を書いていますが、個人ブログでは「React を使いたい人向け」の記事も書いてます。
もし興味があれば覗いていただけると感謝感激です。