2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

フロントエンドの設計で感じたこと

Last updated at Posted at 2024-03-08

はじめに

私が業務を通してフロントエンドの設計(Vue)を行ったときに、悩み・実践を通して感じたことを書きたいと思います。

PJ-Dで実施した設計

当時(多分2022年ごろ)Vueがある程度わかってきた私はプロジェクトが抱える技術負債に直面していました。コピーコードが大量にあり保守性が損なわれているのがその時の状態でした。

技術レベルとしてはVueのコードを実装することはできましたが、経験年数は浅く、どんな改善アプローチが最適なのか検討しているなかContainer/Presentationalパターンを見つけました。

Container/Presentationalパターンとは

ロジックに関心を持つ「Container」とUIに関心を持つ「Presentational」の2つのカテゴリでコンポーネントを分離するフロントエンドのデザインパターンです。

Containerコンポーネントは、データフェッチ、状態管理、ビジネスロジックを担当し、Presentationalコンポーネントにデータとコールバックを渡します。
一方、Presentationalコンポーネントは、受け取ったデータとコールバックをもとに、UIをレンダリングする役割を持ちます。

image.png

この考え方はReactのコア開発者であるDan Abramov(ダン アブラモフ)氏が提唱したデザインパターンであり、React界隈では有名?な設計手法だったみたいです。(React知らないので調べた雰囲気)

※後にPresentational and Container Componentsの記事において自身でこの設計手法を指摘しています。

特に、コンポーネントをこのように分割することはお勧めしません。コードベースでそれが自然である場合は、このパターンが便利です。しかし、私はそれが必要性もなく、ほぼ独断的な熱意をもって施行されるのを何度も見てきました。これが便利だと感じた主な理由は、複雑なステートフル ロジックをコンポーネントの他の側面から分離できるためです。フックを使用すると、任意の分割を行わずに同じことを行うことができます。

Container/Presentationalパターンを実践してみて

ロジックとUIを分離する考え方に当時は「これだ!」と思い食いついたのを覚えています。
ロジックに責任を持つコンポーネントとUIに責任を持つコンポーネントに分け、コンポーネント間はPropsでやり取りする、これだけみるとシンプル設計になりコードの見通しも良くなりそうな気がしていました。
(だって責任を分解するのはフロントエンドに限った話ではなく他でもよく聞く話ですよね?)

実際、このパターンに当てはめてリファクタリングした時は上手く分割できた感触を持っていましたが、後々この方針についていくつか反省点が出てきました。

反省①:共通化

PJ-Dのサービス画面は基本的に同じレイアウトを扱っています。(メインコンテンツエリアに検索条件があり、グラフが2つあり、テーブルが1つある)そのため、画面ごとにレイアウトが変化する訳ではなく、データが変化する、言わばデータ中心の見せ方になります。

ここで、UIに関心を持つPresentationalコンポーネントを各画面で実装した場合、重複したレイアウトを複数作成することに繋がる為避けたいと考えました。
そこで私がとった手段はCommon-Presentationalレイヤーを作成して、そこに頻出するレイアウトコンポーネントを置き、どの画面からも使い回す方法でした。(これも探してそんな考えがあるんだと発見した記憶があります)

image.png

サービス初期段階はこれで上手くいったんですが、サービスが成長し画面ごとの個別対応が必要になるにつれてつらみが現れました。

図にも示した通り、Common-Presentationalは大きなレイアウトを持ち、その子コンポーネントとしてPresentationalコンポーネントを配置しています。そのため、Presentationalコンポーネントに対してPropsを渡して特定の変更させたい場合はCommon-Presentationalを経由する必要があり、結果このレイヤーで画面ごとの個別対応が必要になってきました。つまりCommon-Presentationalが肥大化してきて神ってきました。

今思えば、サービスが永遠同じ状態で続くことはまず発生しないことを念頭にいれて、どう共通化するのがベストか練り込みが足りなかったと感じます。
また、Commonを作成するにしてもSlotで枠組みだけ提供するでも良かったのではないかとも思います。

反省②:階層による見通しの悪さ

分離することでスッキリすることに注意を取られていましたが、実際はPropsの流れやイベントのハンドリングが複雑になり、コードの読み易さが損なわれました。
Containerで状態管理しているdataはどのPresentationalに影響があるのか、PresentationalでEmitしたイベントは親側で何をしているのか…など。
image.png

Dan Abramov氏が指摘したように、コンポーネントとしてレイヤーを分離するのではなく、ロジックはJSとして(Vueならcomposable)切り出す方向性の良かったと感じます。
そもそもVueのSFCは1コンポーネント内でJS、HTML、CSSが完結するとことが魅力なんじゃないか、その強みを崩して分割する必要なかったのでは…と今は感じます。

PJ-Aではこんな設計にしてみた

前PJでの反省点を踏まえて、新しく始めてPJでは共通化は最小限見通しのよい構成を意識して設計しました。
ディレクトリ構成は以下のようになっています。

src/					
┣	apis/				
┣	assets/				
┣	components/				# PJ共通のコンポーネント(Atoms相当)
┃	┣	button/			
┃	┃	┗	xxxButton.vue		
┃	┣	dialog/			
┃	┣	┣	composables/			
┃	┣	┃	┗	useDialog.ts			
┃	┃	┗	xxxDialog.vue		
┃	┗	index.ts			
┣	layouts/				# Slotでコンポーネントをはめ込む枠組み
┃	┣	MainLayout.vue			
┃	┗	MainContentHeaderLayout.vue			
┣	plugins/				
┃	┗	vuetify.ts			
┣	router/				
┃	┗	index.ts			
┣	stores/				
┃	┗	xxx.ts			
┗	views/				   # 画面毎のディレクトリ
	┣	AAA/			 
	┃	┣	component/		# AAAの中で利用する個別コンポーネント
	┃	┃	┗	xxx.vue	
	┃	┗	AAAView.vue		# Routing対象のViewコンポーネント
	┗	BBB/			
		┣	component/		
      ┃  ┗	xxx.vue	
        ┃  composable/   # BBB内で利用する共通ロジック
        ┃  ┗  useXXX.ts
		┗	BBBView.vue	

ポイント

Common-Presentationalのような中間レイヤーを挟まず、画面単位でレイアウトを構成するようにしています。
views/AAA/であればAAAView.vueコンポーネントの中で画面のレイアウトを構成する、views/BBB/であればBBBView.vueコンポーネントの中で画面のレイアウトを行うといった思想です。

また、AAAView.vueの中で機能を開発するとダイアログが必要だったり、他画面にない特殊なUIが必要になるケースがあると思います。そうしたのが出てきたらviews/AAA/component以下にコンポーネントを作成することで、AAAにしか依存しないことを示します。
そうすることで、コンポーネント間の依存関係が凝縮され開発しやすくなると共に、過度にレイヤー化しないのでイベントハンドリングが理解しやすくなるのではと考えています。

image.png

それでもプロジェクト共通で利用したいコンポーネントはでてくると思いますので、その場合はトップレベルのcomponentsディレクトリにコンポーネントを作成します。作成するときもフラットに作成するのではなく、UI毎にディレクトリを作成して視認性を高めます。

※トップレベルで作成するコンポーネントはボタンやセレクトボックスといった副作用を持たない単純なコンポーネントにしましょう。Atomic DesignのAtomsに相当するイメージで、具体的にはVuetifyが提供しているコンポーネント単位です。

さいごに

プロダクトはこれからリリースを迎えるため、この設計が正しかったのか知るのはまだ先のことになると思います。
大事なのは、どんな設計手法をとっても適宜プロダクトに応じた形へ適用させることなので、プロダクトと共に成長させて行きたいと思います。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?