♻️
ReactのSPAのリファクタリングでやったこと
はじめに
同プロジェクトでは並行して設計段階からのリファクタリングを進めており、この記事ではその概要をご紹介し、記事を読まれた方にコード品質の現状が伝わればと思っております。
リファクタリングの背景
Greenfile.work入退場は2020年12月頃に開発を開始し、機能の追加や改善などを進めながら運用してきました。
弊社では今後、Greenfile.work入退場の機能追加や改善にこれまでよりももっと力を入れたいと考えており、その準備としてリリース時から溜まっていた技術的負債の解消を機能開発と並行して進めています。
改善前の主な技術スタックはこちらです。
- React v16.13(CSRのみ)
- JavaScript
- webpack v4
- styled-components
- redux-saga
設計思想
具体的にやったことの前に、フロントエンド全体での設計思想です。
設計を整える目的は「ユーザーへの提供価値向上を加速させる」としました。
提供価値とは各機能の品質や機能数などを指していて、設計を整えることで開発効率を高め、ユーザーへ提供価値を届ける速度を高めたいと考えています。
トータルで見てユーザーへの提供価値向上が加速しているか?
「そりゃそうだろ!」と思われるかもしれませんが、大切にしたいのは加速です。
例えば、変更容易性を高めるために複雑な設計を導入した結果、各実装者がその設計に合わせるのに多くの時間をかけてしまうと、トータルで見た開発効率は下がってしまうかもしれません。
特に弊社の場合は、フロントエンドとバックエンドでエンジニアは分かれておらずチームメンバーも変化する環境の中で長期的にプロダクトを運用していく必要があり、設計の複雑さが開発効率に及ぼす影響は大きくなります。
そのためディレクトリ構成やComponentなどのそれぞれで共通して「設計を整えた結果トータルで見て加速しているか?」を考えて可能な限りシンプルな設計にしています。
そしてその結果、各実装者の設計を理解するための思考リソースを最小化し「よりUXが高いUIは何か?」などのユーザーへの提供価値向上に割ける思考リソースの最大化を目指しています。
やったこと
では具体的な改善内容です。
※一部の改善内容は、後日詳細をまとめた内容を別記事として公開できればと思います。
JavaScriptからTypeScriptへの移行
全てのJavaScriptファイルをTypeScriptに書き換えました。
移行中は拡張子を変えて一時的にで型を付けていましたが、現在は全てのに適切な型をつけ終え移行作業も完了しています。
機能を関心事としたディレクトリ構成への移行
リファクタリング前は「どこに何のファイルを置くのか」が決まっておらず、ファイルを作る時や探す時に迷う時間が多かったため、機能を関心事としたシンプルなディレクトリ構成に移行しました。
詳細は以下の記事をご覧ください。
外部ライブラリの整理
使用している外部ライブラリは開発が開始された2020年12月頃にインストールされたバージョンのままであり、その結果
- 外部ライブラリの新機能が使えない
- 新しいライブラリが使えない
- ライブラリのバグがそのままになっている
- 情報が古いため技術的な調査に時間がかかる
- 脆弱性が発覚した際の対応に時間がかかる
などの課題があり、開発速度の低下につながっていました。
結果的には
- React16からReact18へのアップデート
- webpack4からviteへの移行
- ReduxをswrとuseContextに移行
- 不要なライブラリの削除
- その他のライブラリのバージョンアップ
などを行い、直近のリリース時点では47/51の外部ライブラリのバージョンが最新になっています。
詳細は以下の記事をご覧ください。
State管理手法の見直し
リファクタリング前は、Local State以外のstateはredux, redux-sagaで管理しており
- 新しいstateを定義する時の手間が多い
- キャッシュがされていない
- 関連ライブラリが多くメンテナンスコストがかかる
などの課題がありました。
- Server Cache(API で取得したデータのstate): swr
- Global State(複数のページ間で共有するstate): useContext
- Local State(1 つのページ内に収まるstate): useState
swrのcacheのmutationはv2.0.0で新しく登場したを採用しており、ここらへんの設計話もいずれ別の記事にてご紹介できればと思います。
Global State管理ライブラリを使わずにuseContextを採用した理由
サービスの性質上stateのほとんどはServer CacheかLocal Stateであり、現在もGlobal Stateはほとんどありません。
そのため今はuseContextを使い、今後機能数が増えGlobal Stateの種類が増えてきたタイミングで、用途に合った適切なGlobal State管理ライブラリに乗り換えようと考えています。
Component設計の見直し
リファクタリング前は特にComponent設計が決まっておらず、Componentの粒度がばらばらで1つのComponentが肥大化していたり、共通の概念が複数箇所で実装されていたりと秩序がない状態でした。
そして一番の課題は、基準がないため各実装者がどうやって書くのが良いかが分からなくなっていることです。
そのためリファクタリングを進めながらComponent設計の指針を少しずつ決めていきました。
本記事では概要だけをご紹介し、詳細は後日別の記事でご紹介できればと思います。
Reactの思想に則った実装
全体を通して基本はReactの思想に則った公式ドキュメント通りの実装をしています。
これはReactという大きな技術の上で開発をしており、その技術の思想に則った実装が高い開発効率につながると考えているためです。
Reactに限らず思想に則って正しい使い方で実装して開発効率が出ない場合は、目的に合っていない技術を採用している兆候かもしれません。
Reactの場合は宣言性とComponent指向が最上流の思想のため、その2つを抑えて実装し「Lifting State UP」や「不要なuseEffectを避ける」などの具体的な部分に関しても公式ドキュメントに則った実装をしています。
propsとchildrenを適切に使い分ける
これまでchildrenが使われる機会が少なく、親Componentから孫Componentにデータを渡したい時に、子Componentがそのデータに依存していなくても親→子→孫とpropsをバケツリレーさせてしまい、子Componentが不要な依存を持ってしまうことがよくありました。
そのため
- 子Componentが親Componentに依存しているのであればpropsを使う
- 依存していないのであればchildrenを使う
と適切に使い分ける基準を決めることで、Componentが不要な依存を持たない設計をしています。
Custom Hook
これまではCustom Hookを使ってこなかったのですが、リファクタリング後は積極的にCustom Hookで責任ごとにStateとLogicを切り出しています。
Presentational/Containerパターン(PCパターン)
PCパターンは1つのComponentのViewとLogicを分けて実装する設計パターンです。
Custom Hookが実装されてからは採用されるケースが減ってきましたが、ViewとLogicを分けることで適切な責任範囲でComponentを作りやすいと考え、複数のLogicやStateを持つComponentで採用しています。
全体感の見えるComponent設計
Componentを単一責任に分けるのは大切という前提の上で、不必要にComponentを分けてネストさせるのではなく、Componentファイルを見た時の全体感の掴みやすさも重要視しています。
Hygenを使った雛形ファイルの作成
HygenはCLIの柔軟性が高いため、コマンドの数を増やさず質問文を工夫して色んなパターンの雛形が作れる点が気に入っています。
Hygenを導入した結果副次的な効果として、特に規約として決めなくても自然とComponentやHookの命名や書き方が揃っていったのもよかったです。
ESLintの最適化
リファクタリング以前はextendsにが設定されているのみだったので、主要なextendsを追加しrulesを適切に設定しました。
リンターを細かく設定すると、コードの書き方に関するレビューの指摘がなくなりレビュワーとレビュイーの両方の負担が解消されるため、レビューのコメントでリンターで解決できそうなものがあれば積極的に調べて導入するようにしています。
以下は使ってみて便利だったRuleの一部です。
便利なRule
- react/jsx-boolean-value
JSXでpropにtrueを渡す時に、 or を揃えられるルール。
弊社ではに揃えています。
- react/jsx-curly-brace-presence
JSXでpropやchildrenを渡す時に中括弧をつけるかどうかを揃えられるルール。
弊社では中括弧無しに揃えています。
- react/jsx-no-useless-fragment
JSXでフラグメントを消してくれるルール。
- no-console
consoleに警告を出してくれるルール。
console.logの消し忘れを防げます。
- react/self-closing-comp
childrenの渡されていない時にComponentの閉じタグを省略してくれるルール。
- @typescript-eslint/array-type
配列の型定義を or に揃えられるルール。
弊社ではに揃えています。
- @typescript-eslint/consistent-type-definitions
型定義をorに揃えられるルール
弊社ではに揃えています。
ドキュメント整備
これまではREADMEをメンテナンスしておらずドキュメントも残っていなかったですが、現在はコードを読むだけでは分からない部分をREADMEとnotionに残すようにしています。
逆に言えば全てをドキュメントに頼らず、命名や規則性によってコードの情報量を増やしコードを読むだけで読み取れる部分を最大化した上で、伝えきれない部分をドキュメントに残しています。
具体的にはREADMEに環境構築や実装時に必要なコマンドの一覧を、notionには本記事で触れているような設計思想などを記載しています。
フロントエンド以外の改善
本リファクタリングで行ったフロントエンド以外の改善です。
Kotlin化
Greenfile.workでは開発当初からバンクエンドはPythonのDjangoを使ってREST APIを書いてきましたが、1年ほど前からKotlinのSpring Bootへのリプレイスを進めています。
Greenfile.work入退場もリファクタリング前は全てのAPIがPythonで実装されていましたが、今回のリファクタリングでWebから呼ばれているAPIのほぼ全てをKotlinで書き直しました。
単純に静的型付けの言語に移行しただけでなく、設計もオブジェクト指向でレイヤードなアーキテクチャでありテストコードも完備されたため、以前よりも変更容易性が明らかに高まったと思います。
Autifyの自動E2Eテストシナリオの追加
リファクタリングに含まれるかは微妙ですが、これも今回改善した内容の1つです。
弊社ではE2Eの自動テストツールとしてAutifyを使っていましたが、Greenfile.work入退場はこれまでテストシナリオがなかったため、今回のリファクタリングのリリース時に主要な操作を担保するシナリオを作成しました。
これから改善していきたいこと
最後にこれから改善していきたいと考えていることです。
内製コンポーネントライブラリの改善
今回のリファクタリングでは内製ライブラリのReactのバージョンは最新にしたものの、他の改善はまだまだできていないため今後も改善を続けていきたいと考えています。
テストコード
Autifyで主要なユースケースのE2Eが担保できていたため他の課題に比べて優先度を少し下げてしまった結果、まだテストコードをほとんど書けていないため現在最も改善したい事柄です。
スタイリング
現状は
- Reactのstyle prop
- 内製ライブラリが提供するsassで実装されたUtility Class
- Styled Components
の3つが特に基準の無い状態で混在しているため、これらのどれかもしくはその他の技術の何かしら1つに統一していきたいと考えています。
その他
その他にも
- ルーティング
React Router v5のバージョンを上げるか、違うライブラリに乗り換えるかを検討しています。
- モックサーバー
現状はバックエンドのローカルサーバーを立ち上げないと開発ができずまたテスト的な観点からも、mswなどでモックサーバーを立てたいと考えています。
- 定期バージョンメンテナンス
今回のリファクタリングでほとんどの外部ライブラリのバージョンを最新まで上げることが出来たので、今後は定期的にバージョンをメンテナンスできるようにしていきたいです。
- Open APIなどを使ったAPIスキーマの型生成
- nodeとnpmのアップデート
などなどやりたいことは山積みです。
まとめ
以上が2022年11月頃からGreefile.work入退場で実施したリファクタリングの内容です。
一部、内容の詳細までご紹介できなかった部分に関しては、後日また別の記事でご紹介できればと思っています。
もしフロントエンドの改善に興味がある方はぜひ下記のカジュアル面談からご連絡いただければ幸いです。
さいごに
シェルフィーではエンジニアを中心に積極的に採用をしております!
技術以外にも、「コンテック(Con-Tech)って何?」「面白い?」「働き方は?」といった気になるご質問すべてに正直にお答えいたします。
ぜひ一度お話しましょう!下記からご連絡ください◎