こんにちは!LayerXバクラク事業部エンジニアの加藤 (@tatane616) です。 本記事では、私の所属するバクラク申請・経費精算チームで主に展開しているフロントエンド開発Tipsについて紹介します。
※本記事は LayerX Tech Advent Calendar 2022 4日目の記事となります。
背景
バクラク申請・経費精算チームでは、以前「半年で3倍以上にスケールしたプロダクトチームを支える文化|tatane|note」で紹介させていただいたように、2022年12月時点で5名のエンジニアがいます。
LayerX では厳密に専門性で業務を縛ることは少ないです。プロダクトチームのエンジニアであれば、フロントエンドもバックエンドも(今後はおそらくアプリも)書きます。
その中には今までバックエンドやモバイルアプリを専門にやってきた方もいますが、上記引用の通り、LayerXの開発では全員がWebフロントエンドを実装するような開発体制になっています。
ここで課題になったのが「Webフロントエンドの知識の偏り」です。 全員が、やろうと思えば一人でフロントエンドの実装を進められるスキルはあったのですが、既存の古いVueのコードを参考にしながら実装するしかなかったり、調べ物をするときのググり方の勘所が掴めなかったりで、人によって実装スピードや品質にバラツキがでてしまう状況でした。
やったこと
タイトルにもあるように「フロントエンド開発Tips」なるNotionページを用意しました。
意識したポイントとしては、汎用的・網羅的なWebフロントエンドの開発Tipsではなく「担当プロダクトの直近のフロントエンド実装でつまづかないための開発Tips」を目指したことです。
- チームで推奨している記法のコンポーネントを自動生成する自作便利コマンドの使い方
- Vue2のComposition APIでのPropsの型定義方法
- コンポーネント切り出しの基準
- 紛らわしいライブラリの違いの説明 (
@vue/composition-api
と@nuxtjs/composition-api
など) - Vue周りで困った時の調べ方 (2系と3系が混ざったり、2系のComposition APIではリアクティブシステムが異なったりが紛らわしい)
- CSSによるスタイリング時のハマりポイント
などなど。 これらはTips作成時に併せて実施した「フロント実装で困っていること・理解度アンケート」の回答をもとにブラッシュアップしており、現場の声に応えた(?)内容となっています!
各メンバーの習熟度や、プロダクトを触るメンバー自体は今後も変わってくるはずなので、定期的にアンケートを取ってメンテナンスし続けようと思っています。
具体的な内容
フロントエンド開発Tipsから一部抜粋して紹介します。
Vue2のPropsの型定義
props: { // 必須のPropsにはrequiredをつける requiredStr: { type: String, required: true, }, // 必須ではないPropsにはdefaultを設定する notRequiredStr: { type: String, default: '', }, // nullやundefinedの可能性のある型はPropTypeで明示する nullableString: { type: String as PropType<string | null>, default: null, }, // defaultでオブジェクトを設定するときはアロー関数にする defaultObject: { type: Object as PropType<Hoge>, default: () => defaultHogeObj, }, // defaultで配列を設定するときはアロー関数にする defaultArr: { type: Array as PropType<Hoge[]>, default: () => [], }, }
コンポーネント切り出しの基準
似たようなスタイルのコンポーネントは無理に共通化しない
「テーブルのスタイルが似ている」「フォームのスタイルが似ている」といった場合、汎用化して共通化した方がいいと思いそうになるが、以下の理由から共通化しない方がよい。
- エッジケースに対応しようとして、その場しのぎ的なpropsが増えていく
- それぞれのユースケースに最適なUIを探求しづらくなる
- 無理のある汎用化のため、直感的に使いづらいインターフェースになる
逆に、以下のようなケースは、似たようなスタイルで共通化することが好ましい。
- Buttonなど、最小単位(Atoms)のコンポーネント
- slotで中身のコンテンツを流し込むだけのガワのコンポーネント
同じドメインのユースケースがあるものは共通化する
同じドメインのユースケースを複数の箇所で使いたくなった場合は、以下の理由から、2箇所出てきた時点ですぐ共通化するのがよい。
- 仕様変更があった時に変更漏れを防ぎやすくなる
- 特定のユースケースに絞っているため、使いやすいインターフェースになりやすい
- 説明的なコンポーネント名になって、template内のコードの見通しが良くなる
- template内は行数が多くなりやすく、どこからどこまでが何のための要素なのか解りづらいが、
<UserSearchForm />
<ApprovalRoute />
のように説明的な名前の要素にすることで読みやすくなる
- template内は行数が多くなりやすく、どこからどこまでが何のための要素なのか解りづらいが、
※ スタイルを伴わないロジック部分はcomposition-api (composable関数)で共通化する
基本的な状態管理の考え方
- フロントエンドの状態管理は大きく分けて3つある
- APIデータのキャッシュ
- APIで取得したデータの管理
- ApolloでfetchしたものはApollo側がよしなに管理してくれるため、自前でVuexに詰めて管理する必要はない
- Global State
- ユーザーのロール情報やテナント情報など
- これをVuexで扱う
- ApolloのようなAPI層のキャッシュがあり、GraphQLでページ毎に必要なデータを都度まとめて定義できる環境においては、Global Stateはほとんど使うことはない
- Local State
- VueコンポーネントのDataやrefなどで保持しているステート
- 基本的にはこれで事足りることが多い
- 広めの範囲で状態を持ちたくなった時は、親(祖先)コンポーネントのLocal Stateで実現できないか考える
- APIデータのキャッシュ
- 小さなコンポーネントからVuexを呼ばない(可能であればAPIも叩かない)
- コンポーネントから直接Vuex StoreへdispatchしたりAPIを叩くことで、副作用を持つコンポーネントになってしまい、開発当初は素早く実装できるが後からメンテしづらくなってしまう
- 基本的にはpages配下の親コンポーネントで副作用のある処理を行い、切り出したコンポーネントにはprops経由でデータやハンドラを渡すようにする
- ただし、あまりにもpropsのバケツリレーが辛かったり、副作用が小さいものは子コンポーネントでおこなっても良い
- コンポーネント内だけで利用するデータのGet(Query)だけならOKかなと思っている
おわりに
フロントエンド開発Tipsの内容も含め、LayerXはまだまだ改善しなければいけないことだらけです。
我こそは!という方や、もっと具体的な内容に興味があるという方がいらっしゃいましたら、下記のリンクからカジュアル面談のご応募お待ちしております!