React:関数コンポーネントとクラスコンポーネントの違い
読む所要時間: 12 分
React:関数コンポーネントとクラスコンポーネントの違い
Reactの世界では、Reactコンポーネントの記述方法が2つあります。関数を使用する方法と、クラスを使用する方法です。最近では関数コンポーネントの使用が増えていますが、その理由はなぜでしょうか。
この記事では、関数コンポーネントとクラスコンポーネントの違いについて、それぞれのサンプルコードを使用しながら説明します。この記事を読めば、モダンReactをより深く理解できるようになるでしょう。
JSXのレンダリング
何よりも明確な違いは構文です。それぞれの名前が示すように、関数コンポーネントはJSXを返すプレーンなJavaScript関数で、クラスコンポーネントはReact.Component
を拡張するJavaScriptクラスです。こちらはrenderメソッドを含みます。少し分かりにくいかもしれないので、簡単な例を見てみましょう。
ご覧のとおり、関数コンポーネントはJSXを返す関数です。ES6で導入されたアロー関数について詳しくないという方は、この関数を使用しない以下の例をご覧ください。
関数コンポーネントを使用するrenderはこちら(CodePen)
一方、クラスコンポーネントを定義する場合は、React.Component
を拡張するクラスを作成する必要があります。レンダリング対象のJSXは、renderメソッド内で返されます。
クラスコンポーネントを使用するrenderはこちら(CodePen)
propsの受け渡し
propsの受け渡しは分かりにくいかもしれませんが、クラスコンポーネントと関数コンポーネント、それぞれを使用した記述方法を見てみましょう。以下のコードでは、「Shiori」という名前のpropsを受け渡しています。
関数コンポーネントでは引数としてpropsを渡しています。ここでは非構造化を使用していますが、非構造化を使用せずに記述することもできます。
この例では、名前の代わりにprops.name
を使用する必要があります。
これはクラスであるため、this
を使用してpropsを参照する必要があります。この場合も、クラスベースのコンポーネントを使用しながら、非構造化を使用してprops内でname
を取得できます。
クラスコンポーネントを使用するpropsはこちら(CodePen)
stateの処理
ご存じのように、Reactプロジェクトにおいてはstate変数の処理が不可欠です。最近まで、stateを処理できるのはクラスコンポーネントのみでしたが、React 16.8でReact Hook useStateが導入されてからは、開発者がステートフル関数コンポーネントを記述できるようになりました。フックの詳細については、公式ドキュメントを参照してください。ここでは、0から始まるシンプルなカウンターを作成し、ボタンを1回クリックすると数が1ずつ増えるようにします。
関数コンポーネントでのstate処理
関数コンポーネントでstate変数を使用するには、初期状態の引数を取るuseState
フックを使用する必要があります。この場合は0クリックから開始するため、カウントの初期状態は0になります。
もちろん、これ以外にもnull
、string
、object
など、JavaScriptで使用できるあらゆる初期状態を利用できます。左側では、useState
が現在の状態とそれを更新する関数を返します。このようにして、配列を非構造化しています。配列の2つの要素についてよく分からない場合は、stateとそのセッターだと考えてください。この例では、2つの要素の関係を分かりやすくするために、count
とsetCount
という名前にしています。
関数コンポーネントを使用するstateはこちら(CodePen)
クラスコンポーネントでのstate処理
クラスコンポーネントでのstateを処理する場合、概念は同じですが方法が少し異なります。まず、React.Component
コンストラクタの重要性を理解する必要があります。公式ドキュメントでは次のように定義されています。
「Reactコンポーネントのコンストラクタは、マウントされる前に呼び出されます。React.Componentサブクラスのコンストラクタを実装する場合は、他の記述よりも前にsuper(props) を呼び出す必要があります。こうしないと、コンストラクタでthis.propsが定義されず、バグになります」
基本的に、コンストラクタを実装してsuper(props)の呼び出しを実行しないと、使用したいすべてのstate変数を定義できません。このため、まずコンストラクタを定義しましょう。コンストラクタ内では、stateキーと初期値を使用してstateオブジェクトを作成します。JSX内で、this.state.count
を使用してコンストラクタで定義したstateキーの値にアクセスし、カウントを表示します。セッターもほぼ同じで、構文のみが異なります。
また、onClick
関数を記述することもできますが、setState
関数は、必要に応じてstateの引数であるprops(オプション)を取ることに注意してください。
クラスコンポーネントを使用するstateはこちら(CodePen)
ライフサイクルメソッド
最後にライフライクルについて説明します。もう少しですので頑張りましょう。ご存知のとおり、ライフサイクルはレンダリングのタイミングにおいて重要な役割を果たします。クラスコンポーネントから関数コンポーネントに移行するユーザーにとっては、クラスコンポーネントにおけるライフサイクルメソッド(componentDidMount()
など)に代わるものが何であるか、よく分からないと思います。この目的に最適なフックがありますので、早速見ていきましょう。
マウント時(componentDidMount)
ライフサイクルメソッドcomponentDidMount
は、最初のレンダリングが完了した直後に呼び出します。以前は、最初のレンダリングの前に呼び出すcomponentWillMount
というメソッドもありましたが、今では古い方式とみなされており、新バージョンのReactでの使用は推奨されません。
componentDidMount
の代わりに、useEffect
フックと[]
の2番目の引数を使用します。useState
フックの2番目の引数は、通常は変更されるstateの配列であり、useEffect
はそうした変更時にのみ呼び出されます。ただし、この例のような空の配列の場合は、マウント時に1回呼び出されます。これは完全にcomponentDidMount
の代わりとして使用できます。
ここでの動作も基本的に同じです。componentDidMount
は、最初のレンダリング後に1回呼び出されるライフサイクルメソッドです。
マウント解除時(componentWillUnmount)
便利なことに、useState
フックはマウント解除にも使用できます。ただし、構文が少し異なりますので注意が必要です。それは、マウント解除時に実行される関数をuseEffect
関数内で返す必要があるということです。これは、clearInterval
関数など、サブスクリプションのクリーンアップが必要な場合に特に便利です。大規模なプロジェクトの場合、他の方法では深刻なメモリリークの問題が生じる可能性があります。useEffect
を使用する利点の1つは、マウントとマウント解除の関数を同じ場所で記述できることです。
関数コンポーネントを使用するlifecycleはこちら(CodePen)
クラスコンポーネントを使用するlifecycleはこちら(CodePen)
まとめ
どちらのコンポーネントにも一長一短がありますが、モダンReactではいずれ関数コンポーネントが主流になるというのが私の結論です。
上記の例から分かるとおり、関数コンポーネントの記述は短くシンプルです。つまり、開発、理解、テストがしやすいということです。クラスコンポーネントではthis
が多用されるため混乱が生じやすいですが、関数コンポーネントを使用すれば、このような混乱を回避して、すべてを簡潔にしておくことができます。
また、Reactチームはクラスコンポーネントに置き換わる(さらにはそれを上回る)機能コンポーネント用Reactフックのサポートに力を入れており、利用可能なReactフックが増えていることもポイントです。Reactチームは当初から、不要なチェックやメモリ割り当てをなくして関数コンポーネント内でパフォーマンスを最適化すると述べています。それを裏付けるように、先日、関数コンポーネント用の新しいフックとしてuseState
やuseEffect
が発表されました。しかし同時に、クラスコンポーネントも廃止されないことが確定しています。Reactチームは、今後発生するケースについては関数コンポーネントとフックの段階的導入を検討しています。つまり、一貫性を確保するために、クラスコンポーネントを利用している既存プロジェクトを関数コンポーネントで完全に書き換える必要はないということです。
Reactには多くの有効なコーディングスタイルがありますが、私は上記の理由から、クラスコンポーネントより関数コンポーネントを使用することを推奨します。この記事が、モダンReactを理解するお役に立てば幸いです。Reactの詳細については、公式ドキュメントをご覧ください。また関数コンポーネントとフックの使用例については、Twilio Videoアプリの構築に関する記事もご覧ください。
Shiori Yamazakiは、Platform Experienceチームのソフトウェアエンジニアリングインターンであり、モダンWebアプリケーション開発に熱心に取り組んでいます。連絡先はsyamazaki [at] twilio.comまたはLinkedInです。