社内で「最近のフロントエンド事情」と題してReactを紹介しました
e2infoには”ランチMTG”という社内勉強会とLTが合わさったような制度があります。好きなテーマについて発表する機会がたまに回ってくるのですが、8月下旬の回は私の担当でした。そのときフロントエンドについてお話したので、せっかくなので会社ブログにも記載して公開しておくことにします。
TL;DR
-
- 社内外問わず、新規開発でもjQueryがメインで使われる場面がまだ少なくない
- 要求されるUI/UXが複雑・高機能だと、jQueryでのDOM操作に限界を感じる
- 最近はフロントエンド情勢が安定していて、Reactが実質的なデファクトスタンダード[0]
- Reactはもはやモダンなライブラリといよりも、枯れつつある定番ライブラリに
おことわり:フロントエンド専門というわけではないので、あくまで若手エンジニアの書いた参考情報として見ていただければ幸いです
導入~フロントエンドの背景
私の業務はバックエンドが中心なのになぜフロントエンドの話題なのかというと、実際の開発でjQueryに限界を感じたためです。「今はjQueryでもなんとかなっていて[1]も、高機能なUI/UXを考えたら厳しくなってくるな」という思いがありました。個人的な開発でReactやVue.jsを触ったことがあるため、社内で取りあげてみようと思った次第です。
だいぶざっくりですが、ReactのようなUIライブラリが主流になる前はこのような技術スタックだったかと思います。LaravelでDBの読み書きなどサーバ側の処理を行い、クライアント側に表示するビュー(blade)の構築もLaravelが担当します。この図では、JavaScriptはあくまでHTMLの補助的な役割です。
だんだんと「ユーザが操作するフロント部分を、より使いやすく、より高機能にしたい」となるにつれてJavaScriptの役割が巨大化します。図ではReactやVueのアイコンを入れましたが、それらを使わない場合でもUI/UXを向上しようとすると否応なくJavaScriptの比重が大きくなります。
この結果、エンジニアがデザイナーの成果物(モック)をLaravelに組み込むだけの従来方法では済まなくなります。デザイナーが実際のサーバ側まで想定してJSをコーディングするか、エンジニアがバックエンドとは別に時間をかけてJSをコーディングするしかありません。いずれにしてもデザインでもバックエンドでもない新しい工程およびスキルとして”フロントエンドエンジニア”が一般的になった……
というのが、最近のフロントエンドの流れかと思っています。
jQueryとReactでの実装例
続いて実際に同じUIをjQueryとReactで実装してみたコードを紹介します
最初にjQueryを使って最低限必要な要求仕様①を実装してから、要求仕様②に対応します。その後Reactの実装例を紹介します。
まず、下のようなHTMLを基本構造とします。無機質な見た目だとさみしいので、Bulmaというライブラリを使いました。上図のスクリーンショットにあるUIの見た目はこれで完成です
jQueryで実装
仕様①を実装しました。JavaScriptの文字列にHTMLが入っていることさえ除けば、削除ボタンや追加ボタンを押したときに処理するだけの簡単なコード[2]です。
さらに要求仕様②を実装するとこうなりますが、ちょっとこれはリファクタ前のイマイチな実装です。
修正して、最終形はこちらです。
まだ仕様が単純なのもあって、上の例ではシンプルなコードで済みました。それでも将来的に地雷になりそうな点がいくつかあります。スライドに記載したように「ボタンの有効・無効が切り替わるべきタイミングをプログラマが把握しなければならない」というのが特に大きいポイントです。
また、仕様やUIが増えるとどんどん複雑化していきます。
Reactでの実装例
Reactを使った実装はこのようになります。スライドだと分かりづらいので、StackBlitzにまとめました。package.jsonやmain.tsなどもありますが、主なファイルは src/App.tsx と src/TelField.tsx の2ファイルになります。
https://stackblitz.com/edit/e2lunch202308-react?file=src%2FApp.tsx
Reactで実装したときの1つ目のポイントは下記コードです。配列変数のtels
の分だけ、TelFieldコンポーネントを生成します。そのときにupdateCallbackなどのコールバック関数などをpropsとして渡していますが、削除ボタンが無効となる条件として removeDisabled={tels.length <= 1}
というbool値もTelFieldに渡しています。
<div id="tels-container"> {tels.map((row, index) => ( <TelField key={row.key} tel={row.tel} priority={row.priority} // 子コンポーネントの状態変更は、ここで面倒を見る updateCallback={(tel, priority) => { const newTels = [...tels]; newTels[index] = { key: row.key, tel, priority }; setTels(newTels); }} // 削除ボタンの押下処理も同様 removeCallback={() => { removeTel(index); }} removeDisabled={tels.length <= 1} /> ))} </div>
また追加ボタンについても次のコードのように、無効になる条件を disabled={tels.lengt >= 5}
と記述しています。
<button className="button is-primary" onClick={addTel} disabled={tels.length >= 5} > 電話番号を追加 </button>
このようにReactのコンポーネントでは、ボタンを表示したり非表示にしたりをプログラマが制御する必要がなく、表示・非表示の条件を定義することが自然にできます。
これを言語化してまとめると、下記スライドになりました。
ボタンの有効or無効を1行の定義で済ませることができた一方で、tels
変数を管理するための仕組みが必要になります。今回はuseStateとコールバック関数を使う形でしたが、より実用的にはReduxやRecoilといった状態管理ライブラリを使ったりします。
上のように「jQueryだと厳しい複雑なUIが、Reactだと改善するかも」とお話したのですが、だからといって実際の案件でReactを導入するべきか?というと微妙なところです。
冒頭のスライドで「フロントエンドが1つの工程として比重が大きくなっている」と書きましたが、Reactを使ってもこの工程が簡単に済むわけではありません。品質の高いフロントを作るためには、いずれにしても時間がかかります。
また、ReactはフレームワークでなくあくまでUIライブラリです。そのため、実際のフロントエンドではreact-routerやRedux Tool Kitなど必要なライブラリを導入することになります。最近ではNext.jsのように、Reactを包括したフレームワークの採用例も多いです。Next.jsはサーバサイドもフレームワークに含んでいるため、その場合はバックエンドをLaravelでなくNode.jsにしてしまう方が自然かもしれません。
結言
このように実装コストなどを見ると、Reactを使ってもフロントエンドの複雑さは根本的には解消できないし、特に既存プロジェクトへの導入は難しいと思います。しかし、複雑なUIを実装するのであれば、ちゃんとその複雑さを適切な形で表現するべきです。jQueryではHTML要素の処理や管理が必要ですが、Reactではアプリケーションにとって本質的な情報を管理することになります。上で紹介したサンプルコードでは「要素数を取得して、その数によってボタンを無効化する」という処理を、Reactでは「電話番号の配列数がこの個数のとき、このボタンは無効状態」という定義に変えました。自然とこういった論理的な実装になりやすいため、複雑なフロントエンドではReactを使うのがよいのではないでしょうか。
と、もっともらしいことを書いたのでこの記事はここまでにします。拙い記事でしたがお読みくださりありがとうございました
$(document).on('click', '.remove-tel', ...
で設定する点に注意です。このイベントの登録はページ読み込み後の最初1回なので、「電話番号を追加」ボタンで後から作成した削除ボタンのイベントも拾うためdocumentに設定しています。あまりきれいな方法じゃないですが、button要素のonClickに記述したり、電話番号の追加時にイベントを登録する方法もあります