コンポーネントを純粋に保つ
JavaScriptの関数には純粋なものがあります。純粋な関数は計算のみを行い、それ以外のことはしません。コンポーネントを厳密に純粋な関数として書くことで、コードベースが成長するにつれて発生する、不可解なバグや予測不能な動作の全クラスを回避できます。しかし、これらの利点を得るためには、守るべきいくつかのルールがあります。
学習内容
- 純粋性とは何か、そしてそれがどのようにバグの回避に役立つか
- レンダリングフェーズでの変更を避けてコンポーネントを純粋に保つ方法
- Strict Modeを使用してコンポーネントの間違いを見つける方法
純粋性:コンポーネントを数式として捉える
コンピュータサイエンス(特に関数型プログラミングの世界)では、純粋関数とは以下の特性を持つ関数です:
- 自分のことだけを考える。呼び出される前に存在していたオブジェクトや変数を変更しない。
- 同じ入力、同じ出力。同じ入力が与えられた場合、純粋関数は常に同じ結果を返すべきである。
純粋関数の一例として、数学の数式をすでにご存知かもしれません。
この数学の数式を考えてみましょう:y= 2x。
もしx= 2なら、y= 4です。常に。
もしx= 3なら、y= 6です。常に。
もしx= 3なら、yが時々9や–1や2.5になることはありません。時刻や株式市場の状況によって変わることはないのです。
もしy= 2xで、x= 3なら、yは常に6になります。
これをJavaScriptの関数にすると、次のようになります:
上の例では、doubleは純粋関数です。3を渡せば、6を返します。常に。
Reactはこの概念を中心に設計されています。Reactは、あなたが書くすべてのコンポーネントが純粋関数であると仮定します。つまり、あなたが書くReactコンポーネントは、同じ入力が与えられた場合、常に同じJSXを返さなければなりません:
drinkers={2}をRecipeに渡すと、2 cups of waterを含むJSXを返します。常に。
drinkers={4}を渡すと、4 cups of waterを含むJSXを返します。常に。
数学の数式と同じです。
コンポーネントをレシピのように考えることができます:レシピに従い、調理過程で新しい材料を導入しなければ、毎回同じ料理ができます。その「料理」とは、コンポーネントがReactに提供するJSXであり、Reactがレンダリングするものです。

イラスト:Rachel Lee Nabors
副作用:(意図しない)結果
Reactのレンダリングプロセスは常に純粋でなければなりません。コンポーネントは自身のJSXを返すだけで、レンダリング前に存在していたオブジェクトや変数を変更してはいけません。そうすると不純になってしまいます!
このルールを破るコンポーネントの例を以下に示します:
このコンポーネントは、その外部で宣言されたguest変数を読み書きしています。これは、このコンポーネントを複数回呼び出すと、異なるJSXが生成されることを意味します!さらに、他のコンポーネントがguestを読み取ると、それらがレンダリングされたタイミングに応じて、やはり異なるJSXを生成することになります!これは予測不可能です。
公式y= 2xに戻ると、たとえx= 2であっても、y= 4を信頼することはできません。テストが失敗し、ユーザーは困惑し、飛行機が空から落ちるかもしれません—これがどのように混乱を招くバグにつながるか、おわかりでしょう!
このコンポーネントは、guestを代わりにpropsとして渡すことで修正できます:
これでコンポーネントは純粋になり、返すJSXはguestプロップのみに依存します。
一般に、コンポーネントが特定の順序でレンダリングされることを期待すべきではありません。y= 2xをy= 5xの前後に呼び出しても問題ありません:両方の公式は互いに独立して解決されます。同様に、各コンポーネントは「自分自身のために考える」だけで、レンダリング中に他のコンポーネントと調整したり依存したりしようとするべきではありません。レンダリングは学校の試験のようなものです:各コンポーネントは自分自身でJSXを計算するべきです!
ローカルミューテーション:コンポーネントの小さな秘密
上記の例では、問題はコンポーネントがレンダリング中に既存の変数を変更したことでした。これはしばしば、少し怖く聞こえるように「ミューテーション」と呼ばれます。純粋関数は、関数のスコープ外の変数や呼び出し前に作成されたオブジェクトをミューテートしません—そうすると不純になります!
しかし、レンダリング中にちょうど作成した変数やオブジェクトを変更することは全く問題ありません。この例では、[]配列を作成し、それをcups変数に代入し、その後pushで12個のカップをその中に追加しています:
もしcups変数や[]配列がTeaGathering関数の外部で作成されていたら、これは大きな問題です!既存のオブジェクトを変更してアイテムを配列に追加することになります。
しかし、それらを同じレンダリング中に、TeaGatheringの内部で作成したのであれば問題ありません。TeaGatheringの外部のコードがこれが起こったことを知ることは決してありません。これは「ローカルミューテーション」と呼ばれ、コンポーネントの小さな秘密のようなものです。
副作用を引き起こすことができる場所
関数型プログラミングは純粋性に大きく依存していますが、ある時点で、どこかで、何かが変わらなければなりません。それがプログラミングの要点です!これらの変化—画面の更新、アニメーションの開始、データの変更—は副作用と呼ばれます。これらはレンダリング中ではなく、「脇で」起こるものです。
Reactでは、副作用は通常イベントハンドラの内部に属します。イベントハンドラは、ボタンをクリックするなど、何らかのアクションを実行したときにReactが実行する関数です。イベントハンドラはコンポーネントの内部で定義されていますが、レンダリング中には実行されません!したがって、イベントハンドラは純粋である必要はありません。
他のすべての選択肢を試し尽くし、副作用に適したイベントハンドラが見つからない場合でも、コンポーネント内でuseEffect呼び出しを使用して、返されるJSXに副作用をアタッチすることができます。これは、レンダリング後、副作用が許可されたときに、Reactにそれを後で実行するように指示します。しかし、このアプローチは最後の手段とすべきです。
可能な限り、ロジックをレンダリングだけで表現するようにしてください。これがどれだけ遠くまで連れて行ってくれるか、驚くでしょう!
まとめ
- コンポーネントは純粋でなければなりません。つまり:
- 自分のことだけを気にする。レンダリング前に存在していたオブジェクトや変数を変更してはいけません。
- 同じ入力、同じ出力。同じ入力が与えられた場合、コンポーネントは常に同じJSXを返すべきです。
- レンダリングはいつでも発生する可能性があるため、コンポーネントは互いのレンダリング順序に依存すべきではありません。
- コンポーネントがレンダリングに使用する入力(props、state、コンテキストを含む)を変更してはいけません。画面を更新するには、既存のオブジェクトを変更するのではなく、stateを「セット」します。
- コンポーネントのロジックは、返すJSXの中で表現するように努めてください。「何かを変更する」必要がある場合は、通常はイベントハンドラーで行いたいでしょう。最後の手段として、
useEffectを使用できます。 - 純粋関数を書くには少し練習が必要ですが、それはReactのパラダイムの力を解き放ちます。
いくつかの課題を試してみましょう
Challenge 1 of 3:壊れた時計を直す #
このコンポーネントは、深夜から午前6時までの間に <h1> のCSSクラスを "night" に設定し、それ以外の時間では "day" に設定しようとしています。しかし、動作しません。このコンポーネントを修正できますか?
コンピュータのタイムゾーンを一時的に変更することで、あなたの解決策が機能するか確認できます。現在の時刻が深夜から午前6時の間にある場合、時計の色が反転しているはずです!
