READYFOR Tech Blog

READYFOR のエンジニアブログ

巨大Reactコンポーネントをミニマム工数で安全にリファクタリングした話

巨大Reactコンポーネントをミニマム工数で安全にリファクタリングした話 のカバー画像

この記事は「READYFOR Advent Calendar 2021」10日目の記事です。 Calendar for READYFOR Advent Calendar 2021 | Advent Calendar 2021 - Qiita

こんにちは!READYFORでフロントエンドエンジニアをやっております江面 @neriparkit です。最近は、React/TypeScript を中心に、Next.js 製のSPA、社内UIライブラリである readyfor-elements 、CI/CDの改善、デザインシステムの改善など、さまざまな機能開発・改善に携わっています。

引き続き、要素セレクタ警察の仕事もたまにしています。

要素セレクタ警察の活動の様子
人情派刑事も板についてきました


さて、READYFORでは最近 継続的な支援を集められるようにする "継続寄付" サービスをローンチしました。

この開発にもがっつり参加していたのですが、本記事では、継続寄付の開発の裏側で行った、工数の割に効果が大きかったと感じているコンポーネント分割のリファクタリングについて紹介したいと思います。

業務のコアな部分が密集した巨大Reactコンポーネントの存在

React コンポーネントの設計って楽しいですよね!責務に応じてきれいに分割できたときはテンションが上がります。

しかし、日々開発していると、再設計するタイミングがないままコンポーネントが膨れ上がってしまうこともあると思います。。

READYFORでは、プロジェクト詳細ページのこの部分(画像参照)が最もたくさんの人が目にする、最重要コンポーネントの一つになっています。

プロジェクトページの顔とも言うべき、大事なファーストビュー部分
プロジェクトページの顔とも言うべき、大事なファーストビュー部分

これを今回、継続寄付プロジェクト用にこのような表示も可能にしました。

継続寄付プロジェクトのファーストビュー。青を基調としたUIに
継続寄付プロジェクトのファーストビュー。青を基調としたUIに

スクリーンショットだけだとそこまで複雑に見えないかもしれませんが、このコンポーネントは

  • プロジェクトの分類(通常のもの、寄付金控除型、ふるさと納税型など)
  • プロジェクトの状態(新着、達成、成立、募集終了など)
  • プロジェクトの資金調達方式(All or Nothing 、All in 、継続寄付など)
  • プロジェクト募集終了までの残り時間
  • 第二目標金額(Next Goal)が設定されているかどうか
  • 直近の支援者様からいただいた応援コメント

などなど本当にたくさんのパラメータに応じて表示・機能を切り替えており、すべての情報を props で Rails から受け取って制御しています。

継続寄付の開発が始まった頃、改めてファイルを見たところ、JSX+スタイルで1000行オーバーの巨大コンポーネントになっていました。

大量の条件分岐に打ちのめさr どのようにリファクタリングしたら保守性やパフォーマンスが上がるのか、考えただけでもわくわくしますね〜!

メンテナンス性を上げるためにどうするか考えた

継続寄付は今はβ版です。今後機能を拡充し、どんどんグロースさせていくことが予定されています。

プロジェクトの顔であるこのコンポーネントにも、たくさんの修正が入る予定です。しかし、現状は行数がとても多いだけでなく、if 文やネストがとても複雑な実装になってしまっており、開発の速度を落とすことは目に見えていました。

そこで、継続寄付の開発前に少しでもメンテナンス性を上げるべく、ミニマム工数でなにかできないか模索しました。

正直、このコンポーネントには他にも問題があり...、インターフェースや状態の持ち方からしっかり設計し直したい気持ちもありました。

しかし、リファクタリングにかけられる工数は限られていますし、中途半端に一部分だけ作りを変えると、それはそれで負債になってしまいます。

また、このコンポーネントは社内の他のSPAと比べテストの整備・拡充が遅れており、まだリグレッションテストがありません。(今後やっていきたい...!!!)そのため、あまりたくさんの変更を入れてしまうと予期せぬバグを埋め込んでしまう危険もありました。

ひとまずファイル分割だけを行うことにした

そこで、確実に安全にリファクタリングを遂行するため、方針を以下と決めました。

  • 大規模な設計の見直しは行わない。ファイルの分割だけ行う。
  • is... 系の boolean は基本トップの index.tsx に持たせて下に流す=このコンポーネント内で独自に使っている状態を1箇所にまとめてわかりやすくする
  • Type.tscomponents/ 配下のファイル以外、ディレクトリをさかのぼる import はしない=依存関係をわかりやすくする
  • 命名の改善はしない。前後を追えなくなってしまうため
  • 条件分岐の書き方を変えない。万が一でもバグを埋め込まないようにするため

ここまで「やること/やらないこと」を決めたら、あとは手を動かすだけです。分割した結果がこちら。

├── ImageWrapper
│   ├── ProjectStateRibbon
│   │   ├── ArchivedRibbon.tsx
│   │   ├── NewRibbon.tsx
│   │   ├── NextGoalRibbon.tsx
│   │   ├── SuccessfulRibbon.tsx
│   │   └── index.tsx
│   └── index.tsx
├── Info
│   ├── CurrentStatus.tsx
│   ├── FundingInfo.tsx
│   ├── LatestSupporters.tsx
│   └── index.tsx
├── ProjectMetaInfo
│   └── index.tsx
├── components
│   └── ProjectNotes.tsx
├── Types.ts
├── index.tsx
└── utils.ts

リファクタリングは無事にリリースされ、継続寄付の開発も、大きなストレスを感じることなく遂行することができました。

振り返って

テストがない、抜本的な再設計ができないといった状況の中、バグを出さずにリリースができましたし、まずまずの判断ができたのではないかなと思います。

ポイントとしては、やはり「何をやらないかを決めた」ことでしょうか。段階的に少しずつ良くしていくことを繰り返せればリスクが低いだけでなく、モチベーションも上がりますよね!

あとは、コンポーネントの粒度は「普段から意識していきたい」と改めて強く思いました(自戒も含め)。。可読性の話はもちろん、React の場合、コンポーネントが大きくなればなるほど無用な再レンダリングなどによってパフォーマンス上も不利になりやすいです。反対に、普段から責務に応じたコンポーネント分割を心がけていれば、「この部分を他でも使いたいから共通ディレクトリに移動しよう」などの判断もしやすくなり、細かい改善を繰り返しやすくなると思います。

機能開発のスピードとリファクタリングのバランスは永遠のテーマではあると思いますが、改善を歓迎する風土を礎に、そのときに最適な選択をチームでしていきたいなと思います。

(あと、なによりも、テスト書けるようにしていくぞ...!!!)

最後に

いかがでしたでしょうか!開発の様子が少しでも伝われば嬉しいです。

この記事のコンポーネントではテストがまだないと前述したのですが、社内の各SPAについてはテストを書いていく土壌が整ってきています。大幅改善の内容を紹介した記事も出ていますので、ぜひ併せてご覧ください〜!

tech.readyfor.jp

また、READYFOR 継続寄付でのプロダクトマネジメントについて紹介している記事も出ています。こちらも興味のある方はぜひ〜!

tech.readyfor.jp