こんにちは、LOHACOフロント開発部のちくわです。
今回はLOHACOのフロントエンド技術刷新で使用している技術や実際に触ってみた感想についてお話しします。
背景
LOHACOフロント開発チームでは、Vue2系がEOLになったことをきっかけにフロントエンド技術を刷新しています。
構成
まずは刷新の前後の技術について紹介します。
刷新前
- Vue2系
- Nuxt
- Express
刷新後
- React
- Next.js
- Express
Reactを選定したのは後述の利点のためです。
React
Meta社が開発したコンポーネント指向のJSのライブラリ。宣言的にUI実装が可能であることや、仮想DOMを用いた高速なレンダリングが得意な点が特徴です。
ちなみにDOMとはMDNによると、次のように説明されています。
ドキュメントオブジェクトモデル (Document Object Model, DOM) は、ウェブページを表す HTML のような文書の構造をメモリー内に表現することで、ウェブページとスクリプトやプログラミング言語を接続するものです。
そしてDOMの操作はコストが高いため、メモリ上で保持している仮想DOMと実際のDOMの差分を検出しその差分のみをDOMに反映するという手法をとっています。
利点1 利用率が高い
2024年に14,015人を対象とした調査で、80%の11,212人の回答者が利用していると回答しました。
それだけ多いということは、
- コミュニティが発展する
- ライブラリが増える
- メンテも長くされる
- 採用にも有利
と、長期運用するプロダクトに採用するに当たって安心できる要素が多いといえます。
利点2 UIをJavaScriptで実装できる
ReactはJSに馴染みのある人ならすぐ読み書きし始めることができます。
Vueであれば次のような実装にもディレクティブが必要となります。
<template> <ul> <li v-for="item in items" :key="item">{{item}}</li> </ul> </template> <script> export default { data() { return { items: ['Apple', 'Banana', 'Cherry'] }; }, }; </script>
対してReactであれば次のように、JSの関数を用いるだけ実装できます。
import React from 'react'; export default function App() { const items = ['Apple', 'Banana', 'Cherry']; return ( <ul> {items.map((item) => ( <li key={item}>{item}</li> ))} </ul> ); }
また、同じ理由から実装方法の統一が図りやすいという恩恵も受けられます。
利点3 型チェックが厳密
TypeScriptを合わせて使用すると、型チェックを厳密に受けることができるという利点です。
Reactでは、コンポーネントはすべて関数の返り値であるためこの利点を享受できます。
一方Vueだとtemplateタグ内など型チェックが厳密に働かない部分があり、コードを書いている途中ではエラーに気が付けない問題があります。
こちらもVueとReactのコードを見比べてみます。
VueでcountというpropsをNumber型で定義しています。そして利用先では、文字列を渡しています。しかし、実行するまでエラーは出ず気が付くことができません。
<!-- ChildComponent.vue --> <template> <p>Count: {{ count + 1 }}</p> </template> <script> export default { props: { count: Number, // 型を指定しても、厳密なチェックが行われない }, }; </script>
<!-- Parent.vue --> <template> <!-- `count` に文字列を渡しても警告が出ない --> <ChildComponent count="型チェックされないのだ" /> </template> <script> import ChildComponent from './ChildComponent.vue'; export default { components: { ChildComponent }, }; </script>
一方Reactで同じことをすると、ChildComponentの利用先でTypeScriptの型エラーメッセージが実行せずとも出ます。
interface ChildProps { count: number; } const ChildComponent = ({ count }: ChildProps) => { return <p>Count: {count + 1}</p>; }; export default ChildComponent;
import React from 'react'; import ChildComponent from './ChildComponent'; const App = () => { return ( <> {/* `count` に文字列を渡すと警告が出る */} <ChildComponent count="実行前にわかる" /> </> ); }; export default App;
欠点
しかし、Reactにも当然欠点があります。
- クライアントサイドレンダリングしか使えない
- クライアントでDOMが作られるまで、画面に何も表示されない
- SEOへの悪影響
- 表示速度が遅い
- SPAなので初期表示が遅くなりがち
- パフォーマンスチューニングが手間
- 画像の遅延読み込みやコードの分割などを自分でしないといけない
Next.jsとは
そういった欠点を補うために使用するのがNext.jsです。
Next.jsはVercel社が開発したReactベースのフルスタックフレームワークです。サーバサイドの実装もNext.jsで行うことが可能です。
また、デジタル庁や多数の大規模システムでも利用実績のあるフレームワークです。
利点1 プリレンダリングが利用可能
HTMLをサーバで生成してからクライアントに渡したり(SSR)、ビルドの時点で静的HTMLを生成しておいたり(SSG)などクライアントへ受け渡す前にレンダリングしておくことが可能です。
そのためReactで問題になっていた、初期表示が遅く真っ白な画面が表示されてしまう問題や、 SEOへの悪影響の問題が解消できます。
利点2 柔軟で高速なレンダリング
豊富なレンダリング手法も利点の1つです。
従来のSSRは、CSRとほぼ同じ量のバンドルjsを送信しなければならなかったり、非同期でデータ取得ができなかったりとパフォーマンスに影響が出る問題を抱えていました。
そこで、AppRouterという新しいルーティング機能がNext.jsの13系以降から実装されました。AppRouterを用いることでコンポーネントごとにレンダリング方式を指定できるようになりました。
また、バンドルjsのサイズも小さくなり表示速度の向上が見込めます。さらに、async/awaitを用いた非同期データ取得も可能になりパフォーマンスチューニングがしやすくなりました。
利点3 フレームワーク側でパフォーマンスの最適化を行ってくれる
パフォーマンスの最適化をNext.js側でおこなってくれるという利点もあります。
ページごとにバンドルJSを作成したり、画像のリサイズや軽量な形式へのフォーマット、遅延読み込みをすべてNext.js側で行ってくれます。
これらの機能のおかげで、Reactで問題になっていた不要なファイル読み込みや、パフォーマンス改善の手間がかかる問題が解決できます。
実際に触ってみた感想
ここからはメンバーが実際にこれらの技術を触って感じた、ポジティブネガティブな感想を紹介します。
ポジティブな意見
- TypeScriptを用いたときの型チェックが厳密
- templateタグ内やコールバック関数を指定した際の引数や返り値が曖昧になるといったVueの問題がReactだと発生しない
- Vue3で一部改善が見られたものの、Reactの保守性の高さには及ばない
- JSで書ける
- 今までの知識を流用しやすい
- 独自の書き方が少ない
- importを明示的に書くため、依存先を把握しやすい
- Vueだとプラグインなどグローバルに依存性が注入されており、把握しにくい
- 処理も追いやすい
- 改修の影響範囲も把握しやすい
- hooksのおかげでパフォーマンスチューニングがしやすい
ネガティブな意見
- スタイリングのベストプラクティスがわからない
- 表示パフォーマンスの面でCSS in JS離れが起こっている
- CSS Modulesがパフォーマンス面だと最適となりつつなるが開発体験が劣る
- ゼロランタイムCSS in JSも出てきてはいるが、まだ大規模開発での利用例が見られない
- hooksの理解を深めないと、再レンダリングが多発しやすい
- useEffect, useStateなど再レンダリングを誘発するhooksの知見を深めないとパフォーマンスに悪影響な実装をしてしまう
- React Server Componentを利用しづらい
- クリックイベントの計測を多くの箇所で行っているため、イベントハンドラが設定できないRSCは使いづらい
- Vueと違いフォールスルーで属性指定できない
- 後から部分的にスタイルを渡すことができないため、コンポーネント設計は慎重にならないといけない
- コンポーネントのライフサイクル(表示・更新・削除)が把握しづらい
- Vueだとライフサイクルがドキュメントに明記されているので把握しやすかったが、Reactは詳しい説明がなく習得が難しい
- ReactのライフサイクルはVueとは異なり、hooksに依存するので概念を理解し直す必要がありキャッチアップが大変だった
総括
今回フロント刷新の中でVueとReactを使用し、それぞれに特徴があり、メリットとデメリットが表裏一体であると感じました。
Vueは実装の柔軟さという利点がありますが、反面保守性高く開発することは不得意。
Reactは保守性の高い実装が得意という利点がありますが、柔軟性に欠けると見ることもできます。
技術を利用するプロダクトがどのような特性を有しているのかを把握し、何が適しているのか判断するのが技術選定において重要であることを再認識させられました。
LOHACOは多くのソースコードとリポジトリから構成され、関わっている人数も多いです。そして今後も長期運用していくことを考慮に入れるとやはりReactが適していると考えられます。今後もReact, Next.jsへの知見を深め、LOHACOをさらに魅力あるサービスにしていければと考えております。
最後に
今回はLOHACOの新しいフロント技術スタックやそれらへの感想についてご紹介させていただきました。
新たな知見や課題があった際には改めて記事にしようと思いますので、楽しみにお待ちいただければと思います。
LOHACOのフロントエンド刷新はスタートしたばかりです。もし、LOHACOやフロントエンド刷新に興味を持たれた方はお気軽にご応募ください。
参考資料
https://developer.mozilla.org/ja/docs/Web/API/Document_Object_Model
https://2024.stateofjs.com/ja-JP/libraries/front-end-frameworks/