NEWSニュース

2020.05.08

TECH BLOG

React Hooksをサービスレベルで導入した話

はじめに

はじめまして。鳥山です。

普段はCAMで新規開発を主におこなっているフロントエンジニアです。

 

弊社では長年運用しているサービスが多く、フロントエンドでは主にPHPを用いて開発しているものが数多くあります。

しかし、最近では RiotLitElement などのフレームワークやweb componentsを積極的に導入して新規開発をおこなっております。

 

そうしたなかでJSフレームワークのシェア率が非常に高い React が未導入だったため、それを新規サービスに導入したお話を今回はさせていただきます。

 

導入にあたって

Webアプリケーションを開発していく上で常につきまとう問題。

Stateの管理があります。

 

自分がローカル環境で簡単なモノを作る時には特に意識しなくても良いかもしれませんが、大規模なWebアプリケーションを開発しようとすると各componentで様々なstateをもつことがあり、その分量に比例してコード量も増えていってしまいます。

 

その問題を出来るだけ解消するためにReactが提供している hook を利用することにしました。

この記事ではReact導入の中でも主にhookについてお話させていただきます。

 

実際に運用されているサービス


●MyU (ミュウ
https://info.myu.ooo/

MyUはプロの占い師と繋がり、チャットで占いができるアプリです。

このサービスはユーザーにはアプリで提供されていますが、占い師がユーザーとやりとりする管理画面を Preact で開発しました。

Preactとは、公式が謳っている

Fast 3kb React alternative with the same ES6 API. Components & Virtual DOM.

の通り、Reactと同じES6のAPIを使っており、3kbと軽量であることが特徴です。

今回の記事では詳しい説明を省かせていただきますが、Reactとほぼ同じ機能を有しており、Reactよりも軽量で利用することができます。

 

 

●marouge (マルージュ https://marouge.jp/

marougeは「明日のなりたい自分に」をテーマに恋愛や占いなど、ユーザーの悩みに寄り添ったコンテンツを届けるサービスです。

こちらはReactを元に構成されている静的サイトジェネレーター GatsbyJS を用いて開発しました。

 

React Hooksとは

 

・関数コンポーネントとクラスコンポーネント

 

React16.8以前まではstateはクラスコンポーネントだけが持っており、関数コンポーネントではstateを扱うことができませんでした。

しかしhookを導入することにより関数コンポーネントでもstateを扱うことができるようになりました。

 

「クラスコンポーネントを使えば良いのでは?」っていう疑問が残る方もいると思いますが、

関数コンポーネントで書けばコード量が減り、完結で見やすくなります。

図1: クラスコンポーネントと関数コンポーネントの比較

 

・hookを使う

 

hookでstateを扱う簡単な例をご紹介しましょう。

 

以下の図はボタンをクリックしたらカウントが増えていく、よくあるコンポーネントです。

図2: hookを使った例

 

4行目のuseStateがhookの機能の一部であり、関数コンポーネントでローカルなstateを扱う時に使用します。

hookの各機能は 公式ドキュメント で詳しく説明されているので、ここでの説明は割愛させていただきます。

 

以降は、サービスレベルでhookを扱った時の手法を紹介します。

 

React Hooks Tips

私はReact製のプロジェクトを構築する場合、hookが関わる部分は主に2つだと考えています。

1つは各ページの表すページのコンポーネント、もう1つはページに利用するボタンやフォームなどのUIのコンポーネントです。

図3: フォルダ構成

 

1.カスタムフックの基本

 

図2の例ではButtonコンポーネントにstateを持たせていますが、UIコンポーネントはpropsに渡ってきた値をただ扱うだけが望ましいです。

UIコンポーネントで複雑なstateの処理を持たせると可読性が下がりますし、

色んなコンポーネントでstateを処理するとライフサイクルの流れが読みにくくなってしまいます。

 

ではどうすればいいかというと、カスタムフックを作ってstateの処理をまるごと切り出してしまうのが便利で管理もしやすいです。

以下は簡単なformをもつページのコンポーネントです。まずはカスタムフックを使用しない場合の例を見てみましょう。

図4: formをもつページ

 

順に説明します。

 

●4行目

inputに入力されるnameをstateとして保持します。

 

●6-8行目

ネットに落ちているサンプルなどを見るとinputのonChangeの処理を関数にせずに直接

 onChange={e => setName(e.target.value)} 

のように書いてるものがありますが、この書き方だとeslintでerrorがでてしまうので正しくありません。

回避策として useCallback を使います。

これは関数コンポーネントに限らず、クラスコンポーネントでも同様です。

クラスコンポーネントではこれをbindして回避したりします。

 

●10-16行目

submitするとnameが出力されます。第2引数で

 [name] 

としているのは、関数コンポーネントは内部に何かしら変更が加えられる度にコンポーネント全体が更新されるので、handleSubmitも毎回更新されてしまいます。更新頻度を減らすために第2引数の配列にnameを入れることでnameが更新された時のみhandleSubmitを更新することができます。

 

このままでも問題はないのですが、これを全てのコンポーネントでおこなっていくとコードはどんどん肥大化していってしまいます。

そのためinput部分の処理をカスタムフック化します。

図5: inputの処理をカスタムフック化

 

ついでにinputの部分をUIコンポーネントとして切り出します。

図6: inputを別UIコンポーネント化

 

これらを先ほどのformに適用すると

図7: カスタムフックを導入したformページ

 

先ほどよりだいぶシンプルになりました。

カスタムフックにbindを用意することでTextInputに渡すものとFormPageの方で使用するvalueを切り分けることができ、より完結にできます。

 

この例だとカスタムフックにしてもそこまで差がありませんが、

ページ内で様々なstateを扱うようになればなるほどその差は顕著となっていきます。

inputが2つになった場合でもカスタムフックは使い回せるのでよりコード量が減ります。

図8: inputが2つに増えた場合

 

フォームの値はReactに制御させるのが好ましく、ドキュメント通りだとこのような 感じになるが、

関数コンポーネントでカスタムフックを用いると図8のように再利用性も高くなります。

 

2.カスタムフックの応用

 

カスタムフックはUIコンポーネントの処理だけでなく、util的な機能としても用いることができます。

図9: windowのsizeをobserveして返すカスタムフック

 

これはwindowのsizeがresizeする度に値を更新して返してくれるカスタムフックです。

これを使いたいコンポーネントで呼び出せば常にwindowのsizeを知ることができるようになります。

 

3.useEffectの扱いには細心の注意を

 

hookを扱う上で特に注意を払わないといけないのが useEffect です。

useEffectに作用している変数をちゃんと管理させないと無駄な更新がはしってしまい、パフォーマンスが悪くなってしまいます。

図10: useEffectの扱いについて

 

useCallbackにも言えることですが、useEffectは第2引数に何を入れるかで挙動が全然違います。ここを意識せずに書くと予期せぬところで値が更新されたり、またはその逆が起きてしまう危険性があります。

 

図10の例を見てみます。

このコンポーネント内でのuseEffectの扱いとして正しいのは21-23行目のuseEffectとなります。

11-13行目のuseEffectは正しいcount数がtitleに表示されますが、何かしらの更新のたびにここが呼ばれるので無駄な処理がはしってしまいます。

16-18行目のuseEffectは初回しか呼ばれないのでcount数が増えても以降一切呼ばれません。

正解である21-23行目のuseEffectはcountの更新のときだけ呼ぶようにしているので正しい動きとなります。

 

このような値を1つしか扱わないuseEffectなら間違える可能性も低いのですが、実際のサービスレベルだと複数の値を扱うことが多いので一つ一つ気をつけなければなりません。

そのようなミスを機械的に防ぐ方法としてhook用のeslintも用意されているのでそちらを利用すれば未然にミスを防ぐこともできます。

eslint-plugin-react-hooks

 

4.グローバルな state management

 

規模がそこまで大きくないサービスであれば、フル機能が備わっているReduxをわざわざ利用しなくてもhookのみでReduxライクなstate managementが実現できます。

 

Store機能はcreateContextuseReducerを使うだけの少量なコードで生成できます。

図11: hookを利用したStore機能

 

reducerにおいてはReduxのreducerとほぼ同じように定義できます。

図12: reducer

 

そして大枠を<Provider>で囲ってあげればその中のコンポーネントに継承することができます。

図13: App.jsにStoreをProvideする

 

そして各コンポーネントでuseContextを使ってstateとdispatchを呼び出して値の取得と更新を行うことができます。

図14: 各コンポーネントでstateを使用する

 

以上のようにhookを使って簡単にReduxに近い機能を有した簡素なstate managementを構築することができます。

 

まとめ

今回、React Hooksをサービスレベルに導入した際に貯まった知見を記事にさせていただきました。

 

React Hooksがリリースされて約1年ほど経ちましたが、機能も充実しており一度開発経験を踏めば

それ以降はスムーズに開発をおこなうことができると思います。

React開発は今後の主流になっていくと思うのでみなさまのReactライフに少しでもお役に立てれば幸いです。

最後まで読んでいただきありがとうございました。

 


 


WRITTEN BY:鳥山
社内のフロントエンドの開発。主にReactを用いての新規開発をおこなっております。