TS, React未経験からスタートするReactプロジェクト
概要
TypeScriptS初心者(cdkでたまに触る程度)かつReact開発経験がないメンバーで、Vue2のプロダクトをReactで作り直した。その時にReactで開発するためにやってきたこと、技術選定をまとめた。規模としては4名のスクラムチームで、ReactはSPAで作っている。新規にReactで開発をはじめる方でもそうでない方でも参考になると良いです。
根底にあるのは、対象がReactでもVue3でもなんでも良く、新しいフレームワークで開発する際に、どうやって学習して適応して切り抜けるかということ。カオスエンジニアリングと似ていて、フレームワーク移行に対応できるチームですか?という検査の観点もある。こういった変化を凌いだ経験があれば、次の移行にも多分対処できると期待。
モチベーションはこんな感じ。
- TypeScript使いたい
- フロントエンドで自動テストを増やしてe2eテストを減らしたい
- アトミックデザインからFeature-Based Architectureへ
このあたり考えながら戦略を付箋でかいたり。
一回じゃ固まらないから、どれだけ作り込む前にスケルトン状態で改善ループを回せるかが重要だと捉えていた。
TypeScript使いたい
バックエンドでPythonを使っていると、フロントエンドのJavascriptでも型が欲しくなった。型で抑えることで、単体テストの観点を減らしたい、補完や型チェックによって可読性を上げ認知負荷を下げたい、という動機。
フロントエンドで自動テストを増やしてe2eテストを減らしたい
デプロイ後にバックエンドと連携してe2eテストをしていたが、テストケースが増えてきた。手動もPlaywrightも使っているがどちらにしてもメンテ工数が肥大化。
そもそもフロントエンドのテストはほとんど自動化されておらず、手動でおこなっていた。フロントエンドで単体テストを増やして観点を押さえればe2eケースを減らせる、と期待。フロントエンドテストについてはこの本をメンバー各々で読んだ。
アトミックデザインからFeature-Based Architectureへ
アトミックデザインは使いこなせなかった。
とくに結合部分が認識しづらくなって保守性が下がってきた。ユースケースとの親和性が低い感じ。
なので次はFeature-Based Architectureにしてみようと決意。
bulletproofではこんな感じで、feature以下にapiとコンポーネントが格納されている。
ボタンなどの共通はcomponentフォルダにある。非常に直感的でわかりやすい。
方針
まず、理解が浅い状態では品質の悪いコードを生成することは経験上骨身に染みていた。
なので理解度を高める必要があった。効率がいいのは先人の知恵から学習すること。
以下が全体方針。
- bulletproofを理解しながらそれをベースとしてカスタマイズすること
- 開発速度を早めるためフロントエンドで完結して開発できるようにすること
- 必要なライブラリを選定すること
- モブプロでやること(スクフェスリンク貼るか)
スキル練度を高める目的もあったので基本的に全ての開発はモブプロで行っていた。
これは分散学習の効果もあり非常に効率的だった。
以下は規約的なもの。
- わからなくなったらbulletproofを参考にすること(足向けて寝れない)
- 機能を作り込む前に大量の検査に晒すこと(レビュー)
- 型にまけないこと(strict true)
- 説明できないコードをプロダクトコードに入れないこと
まずある程度はbulletproofを読めるようにしなければならないが、型についてあまり知らない。
そもそもTS初心者でどう開発する?
TSに慣れる
typechallenge
まず実行したのはtypechallenge初級(中級以上はもういいかな)。typechallengeがいいところはコード動かしながら挙動を学べる点。調べずに解けるくらいまで、毎日30分とって全員が取り組んだ。一月くらい。
Reactでもジェネリクスを頻繁に使うのでこれは本当にやってよかった。
公式ガイドやサバイバルガイドなどは辞書的に活用。
地味だけどconfig
tsconfigドキュメント読んで良さそうなもの追加していった。最初だから緩くするのではなく、最初だからなるべく厳しい方向に寄せた。何が良いかわからない初心者こそそうすべきかなと。でも度々敗北者になりかけた。
あとは色々Prettierの設定とかしているけど面倒くさい。どんどん設定がカスタマイズされて煩雑になって管理が面倒。ここに時間つかいたくない。Pythonみたいにruffみたいになってほしい。Biome使うかという話も最近でている。
bulletproof参考
一個一個、ボタンコンポーネントつくるとか、featureコンポーネントつくるとか、必要そうな部分を理解しながらプロジェクトに導入していった。bulletproofは一行一行の理解に努めた(コントリビュータに感謝しながら)。わからない時はcloneしたbulletproofのリポジトリでcopilotにコードの意図を聞いたり。これはOSSのコード理解を進める上で非常に有効だった。こちらの理解が正しいか壁打ちしたり、このcopilotの使い方は便利だった。package.jsonで知らないパッケージないかとかも一通り調べたり、隅々にわたって読み込んだりした。
bulletproofは日本でもいろんなところが紹介している。
ある程度bulletproofの構造や思想を理解してくると、次は実際のビジネスロジックを反映したコンポーネントをつくることにした。その際はバックエンドをMockしてコンポーネントの動作を確認する必要があるので、スキーマ駆動で開発をすすめることにした。
スキーマ駆動
フロントエンドで完結して開発するためバックエンドのMockが必要。Mockの整合性をとるスキーマが必要になる。
スキーマはOpenapi定義書から作成する型を用いることにした。(openapi-typescript)
スキーマ生成ライブラリはたくさんあるが、fetchも提供していてカスタマイズ性が高くミニマムな導入をしやすいこと、npmトレンドなどを考慮して採択した。これはTS初心者が多いこともあり、あまりラップされていないプリミティブな型であるほうが学習にも適していると考えたため。
テスト戦略
Integration(featureコンポーネントのテスト)を厚くすること。単体テストはコンポーネント以外で関数など定義しているものだけ。方法はStorybookによってStoryを記述してテストすることにした。
テスト時にはバックエンドをMSWでMock化している。openapi定義書から自動でMockを作成するライブラリも候補ではあったが、レスポンスを状況に応じてコントロールしないと挙動を確認できないものがあるため、MSWを選定した。エンドポイントに対して全てMock関数を書く必要があり面倒に思ったが、型保護もあるので想像以上に楽をできた。
Storybookが嬉しいのは、コンポーネント単体で描画して確認できる点。例えばターミナルでテストがパスとしても、見えている範囲で本当に意図した挙動になっているかはわからない。その点、見えるテストというのはフロントエンドにおいては非常に有効に感じた。
最近以下の記事を見つけて読んだところ非常に近しい考えを感じた。とてもわかりやすく言語化されていて理解しやすかった。ビジュアルリグレッションはまだ取り入れていないので、ROI考えて検討してみたい。
その他ライブラリ選定
Form
ReactHookFormを用いた。
Formikとの比較をしたが、再レンダリング防止によるパフォーマンス、メンテナンス頻度をみてRHFにした。
Formライブラリいる?という意見もあった。state管理しないで済むということ、バリデーションの連携性によるメリットを確認して導入判断した。公式ドキュメントは趣味として一通り読んだ。
バリデーション
Zod。YupとSuperstructなどが候補だった。TSとの連携性、メンテナンス頻度によってZodを選択。
バリデーションロジックの分散を防いだり、ライブラリのロジックを安全に使えるという点で導入。
公式ドキュメントは趣味として一通り読んだ。
TanstakQuery
対抗馬なくこれでいいかという感じ。
上手く使えばいいんだろうけど、Vue2の状態管理で痛い目にあっていたのでReduxなど状態管理ライブラリはやめた。TanstakQueryはメンバー全員で公式ドキュメント読み会を定期的に行った。非同期で学習した内容をMiroに書いて最後に共有みたいなことを週一で一時間。公式ドキュメントは充実していて(作者インタビューもある)、codespacesで挙動確認もできる。感動したのはOptimisticUpdate。これでユーザー体験向上できると思った。
tanstackQueryのインポートはapiディレクトリ以下からしかできないように制限している。これらによって結合箇所の制限が制限され、巨大な泥の塊になることを防いでいる。
UIライブラリ
MUIにした。悩んだ点。shadcnとMUIで悩んだ。いまでも考える。
ヘッドレスUIはチャレンジだったが今回は見送った。理由はそこまでカスタマイズに時間をかけられないと判断したから。でもshadcnの思想は面白くて、installするのではなくgithubのコード自体を持ってきて使う(ライセンス的にどうだろ)。カスタマイズはおのおの利用者がやってねという感じ。MUIは使い慣れているというだけ。
ここでも気をつけているのは、componentの共有コンポーネント(例えばボタン)などだけがMUIに依存してよく、featureコンポーネントからはMUIの読み込みすら禁止している(eslintの設定、no-restricted-paths)。つまり、tanstackQueryと同じく、今後ライブラリを変える時がきても、やることはただ自作しているプリミティブなコンポーネントの内部を変えるだけで済むようにしている。
plop
コンポーネント生成ライブラリ。地味に便利だが整備工数がペイするかわからない。
完走した感想
型パズルが結構発生する。とくにライブラリを使っているとおきる。MUIのDatePickerとReactHookForm、Zodの連携時なんかは結構ややこしい型パズルが発生した。ここで役に立ってきたのがtypechallengeの経験。extendsとか直感的ではなく馴染みがないと難しいところも切り抜けられた。
ライブラリについて見直すと、npmのトレンドとGithubのメンテナンス頻度、スター数はかなり選定に関わっている。ライブラリへの依存についてはかなり慎重。tanstackqueryもMUIと同様で、apiディレクトリのuseHogeみたいな自作apiの入出力さえ担保できれば別のライブラリに切り替えられる。つまりFeature-Based Architectureという構造にコードを依存させて、細かいライブラリはあとで切り替えるようにしている。移行は小さいもの(ライブラリ)と大きいもの(フレームワークや言語)があるので、小さいのはこうして押さえておくと耐久性を上げられる。
スプリント毎の検査に他チームの開発者を呼んだのは効果的だった。別にTSとかReactに詳しいわけではなくても、こちらが説明を試みることでみつかる綻びもあり、質問により発覚する観点も多くあった。こういった点に早く気づいて、作り込む前に修正して対応していくことが有効に感じる。何かの本で、設計の良さとは決断をどれだけ後回しにできるか、ということだみたいなのを思い出した。
ある程度、自分達のプロダクトが最大公約数的でもうまく機能するだろう内容なので、全体的にトレンドに乗っている感はあるかもしれない。
結論
今後フレームワーク移行や新しいもの使う時に考えたいこと。
- ある程度信頼をおけるOSS(特定のフレームワークの集合知bulletproofみたいな)を見つける
- 移行前の問題点に対する解決策を用意する
- 初期はリソース効率<パス効率で全員がスキルを身につけることで今後に備える
ある程度知見が溜まってそれを説明できるメンバーがいる状態になれば、他のプロジェクトもそれを参考に移行したり効率的なスキルアップができる。ただ信頼のおけるOSSの発達には時間がかかるのですぐには導入できない。
bulletproofにはお世話になりっぱなしなので、コントリビュートする側にまわれるようにもっとReact頑張ろう。
Discussion