BUYMAフロントエンド開発にRecoilを導入してみた話

この記事は Enigmo Advent Calendar 2022 の 5日目の記事です。

こんにちは。フロントエンドエンジニアのWooです。 エニグモへ入社して3年目、主にBUYMAの購入者側のページをReactで構築しています。

BUYMAではReactのグローバルステート管理のために主にReduxを使用していますが、今回は新しい取組としてRecoilを導入し、開発を行なってみましたので、その経験を共有しようと思います。

まず、軽くRecoilについて理解し、Recoilを導入するようになった理由、Recoilで開発する時に良かった点や困ったこと、他にリリースする際に失敗した話、最後にはこれからRecoilのより良い開発経験のための整理などをこの記事でお話ししたいと思います。

Recoilを軽く理解する

RecoilはReactプロジェクトのための数多いグローバルステート管理ライブラリの中の一つでFacebookが2020年5月に出したライブラリです。なので、Reactを作ったFacebookが出したRecoilは他のライブラリ(Redux, Mobx)とは違い、React専用ながらReactに最適化されたと言えるライブラリです。

Recoilを導入するようになった理由

2022年2月、BUYMAではスマホウェブの検索絞り込み画面の使用性を高める企画が始まりました。
従来の画面はかなり前に作られたものだったので、画面の機能改善ではなく、新しい検索絞り込み画面を開発する方向に進められました。
私は新しい検索絞り込み画面のステートをどう構成すれば開発しやすくなるのかを悩みながらステート設計方法などを探していました。
そんな中、私はRecoilに接するようになりました。
新しく作る検索絞り込み画面は機能は多いながらも操作性は単純で軽い画面になる必要がありました。
Recoilはユーザーの絞り込み条件により、変更される数多いステートを柔軟に拡張・分解しながら開発ができそうと思いました。
さらにReduxに比べコードの量も多く減らすこともでき、少人数でもスピード感ある開発ができそうと思いましたので、検索絞り込み画面開発に適合だと判断、導入を進めるようになりました。

Recoilで開発した時の良かった点

まず、RecoilはReactのSuspenseと一緒に動作するようにデザインされていました。 コンポーネントをSuspenseで囲むと非同期処理などでまだ保留中の下位コンポーネントをキャッチし、代替するUIをレンダーしてくれました。 これにより、データを取得している間のローダー表示を全体的に統一することができました。

その他、ステートを使う時にどんなエラーが生じるかの定義が簡単でした。それはErrorBoundaryでキャッチする仕組みでした。 ErrorBoundaryはエラーをキャッチし、エラーを記録し、クラッシュしたコンポーネントツリーの代わりにフォールバック用のUIを表示するReactコンポーネントでした。

グローバルステートの設定と定義が非常に簡単で、ステートの使用はRecoilが提供するHooksを利用、ステートをget/setすることだけでした。React Hooksの文法と似ていることで、初めてRecoilを書くエンジニアでもすぐになれる利点がありました。

また、グローバルステートを使用するためのボイラープレートの量が非常に少なく、全体的にラーニングカーブが低いというメリットがありました。

そして、ステートの変更や定義が頻繁に行われても既存コードとの影響度が低いため、開発要件によるステートの変更でも柔軟に対応が可能でした。

propsを渡さなくても良い特徴ではコンポーネントリファクタリングや分割などが容易でした。

APIの非同期の処理ではユニークなインプットがある時のみ実行されるようにキャッシュされる処理があり、ユーザーの同じアクションを防ぐなどの実装を考慮しなくてもよい便利さがありました。

Recoilで開発をする時に困った経験

ユーザの絞り込み条件変更によるReactの処理では、Suspenseを利用しました。
SuspenseはAPIの非同期処理を待機中の下位コンポーネントの代わりにローダーをレンダーしてくれましたが、実際に動作を確認すると絞り込み条件変更の度に表示される真っ白のローダー画面が与える印象は求めている操作性の軽い画面とは違うように感じられました。

非同期データを使う最小限のコンポーネントをSuspenseで囲むコンポーネント構造が設計可能であれば、全画面ローダーを表示する必要はないかもしれません。

しかし、検索絞り込み画面はヘッダー以外のところが非同期データによってレンダーされるコンポーネントのため、前述の方法の設計ができませんでした。

そうして考えた方法は、単なるスピナー表示ではなく、スピナーを絞り込み項目と一緒に見せる方法でした。
スピナーに絞り込み項目も含めSuspenseに渡すことになったので、このローダーコンポーネントは肥大化されましたが、パフォーマンスに大きな影響はありませんでした。各コンポーネントごとにステートを使っているおかげで、リファクタリングはJSX部分の簡単な修正で済みました。

refinement.gif (4.1 MB)

Recoilで開発したコードをリリースする際に失敗した話

BUYMAのReactコードは、babelを通してES5コードに変形した後、もう一度uglifyを行う過程を経ることになります。 このES5に変形する過程ではnode_modulesを含みません。

問題はnode_modulesにあるRecoilライブラリをOOO_app.jsの結合ファイルに含めてuglifyしようとした時に起きました。
uglifyはES6コードを解析できず、圧縮に失敗していました。
Recoilライブラリは主にES6で書かれていたのが原因でした。
リリースの過程でしか確認できないファイル圧縮の失敗は予想できなかったのです。

結局、他の代案を探さなければなりませんでした。
RecoilライブラリをES5に変形する方法など考えてみましたが、おすすめしない方法だとネット上では勧告していました。
近年BUYMAIEをサポートしなくなりましたので、結合のファイルをES5に変更する過程をなくす方法もありました。しかし、その方法は影響範囲が多く、少人数で解決できないと考えられました。

そんな中、BUYMAはReactライブラリをCDNロードする方式で運用されていることが思い出しました。 近いところの一番簡単な方法が見つからず、遠いところの難しい方法だけを考えていました。

結局、RecoilのCDNを利用してuglify問題を解決する方法を採用することになりました。

Recoilのより良い開発経験のための整理

最近はまた別の画面でRecoilを使って開発をしました。

2回目の開発経験では、命名部分の理解度を高めるとRecoilを知らないエンジニアでもコードが理解しやすいかもという考えを持ちながらコードを作成しました。

selectorは以下のように関数の名前を命名しました。 意味的に派生したステートの名前を命名しようとしました。

コンポーネントでは、上記の派生したステートの名前を簡単に理解できるようにコードを作成しました。

Recoilのフォルダとファイルの構造はステートの単位を意味するatomsと派生のステートを修正して新しい結果を作るselectorsに分離して使用することが便利でした。

/api
/atoms
/components
/containers
/hooks
/selectors

Recoilの開発の際にはReduxの開発の際と同様、意図しないステートの更新などを確認する必要があるので、開発ツールとしてDebugObserverは必須だと思います。
ReduxのDevToolほど強力なツールではないと思いますが、シンプルな更新ログの形でいつもChromeのDevToolsを開いて開発する私には見やすくてわかりやすかったので、不便ではありませんでした。

終わりに

Recoilに対する小さな経験を話すことができて嬉しいです。
今度機会があればRecoilの多様なユーティリティ機能の使用経験を整理してみたいと思いました。
開発ツールの不在などReduxよりパワフルではないと思いますが、Reactフレンドリーな書き方や簡単な非同期処理など、開発しやすいRecoilライブラリの長所と魅力をこの記事で少しでも伝えられましたらと思います。

関連記事

株式会社エニグモ すべての求人一覧

hrmos.co