vue-build-feature について
vue-build-feature は、データアクセスやコンポーザブルからコンポーネント、ビュー、ルートまで、複数ファイルにまたがる Vue 3 機能の構築を案内するライブラリ非依存のガイドです。単一コンポーネントではなく機能全体を扱う開発者向けに、責務の分離とボトムアップの構築手順を示します。
主な機能
- 01データ、状態、表示の分離
- 02コンポーザブル経由の機能データ読み込み
- 03依存関係に沿った各層の構築
- 04機能フォルダーとバレルの整理
- 05ルートと共通処理の設定
活用シーン
- 01複数ファイルにまたがる Vue 3 機能の計画
- 02API 連携ページとコンポーネントの構成
- 03読み込み・エラー状態を持つルートの追加
- 04オーバーレイルート間の共有状態の調整
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ basmilius/ skills --skill vue-build-featureインストール後、Claude Code で/vue-build-featureを入力して読み込まれたか確認します。
詳細
Vue の機能開発
1 つのコンポーネントに収まらない Vue 3 の機能、つまりページと、それを構成するコンポーネント、コンポーザブル、データアクセス、ルート、およびファイルをまたいだ連携方法について説明します。特定のライブラリには依存しません。個々の .vue ファイルの作成方法については code-style の Vue リファレンスを参照してください。UI コンポーネントの選定(プロジェクトで Flux を使用している場合)には flux-ui を参照してください。プロジェクト固有のルール(状態管理ライブラリ、権限、i18n の配置場所)は、リポジトリの CLAUDE.md / AGENTS.md に記載されています。
1. 基本原則:ビューが調整し、コンポーネントが表示する
- ビュー(ページ)が唯一のオーケストレーターです: 機能コンポーザブル(セクション 5)をインスタンス化して、データ、状態、ハンドラーを管理します。状態はコンポーザブルに置き、ビューだけが props /
v-modelを介して子コンポーネントに接続します。子コンポーネントが正規の状態を持つことはありません。変更を求めるためにイベントを発行するか、モデルに書き込みます。 - プレゼンテーション用コンポーネントは、props を受け取り、イベントを発行します: 使用するのは
defineProps、defineEmits、defineModelのみで、独自にデータを取得しません。 - 分割し、詰め込みません。 ツールバー、フィルター、カード、リスト、合計、空状態など、マークアップの多いブロックは、単独で成り立つようになった時点でそれぞれ専用のコンポーネントに分割します。
2. 1 つの機能を構成するレイヤー
複雑な機能では、通常、次のレイヤーにまたがります。それぞれを分離してください。
| レイヤー | 含まれるもの |
|---|---|
| データアクセス | 型/モデルと、API と通信するサービス/クライアント |
| コンポーザブル | ビュー向けのデータ取得、読み込み/エラー状態、派生状態 |
| コンポーネント | 機能フォルダー内のプレゼンテーション用パーツとバレル |
| ビュー/ページ | オーケストレーター:状態を管理し、コンポーネントを組み合わせます |
| ルート | ルーターのエントリー(遅延インポート、ガード、メタデータ) |
| 横断的要素 | 共有ユーティリティ/フォーマッター、アイコン、ユーザー向けテキスト(i18n) |
各レイヤーの配置場所はプロジェクトによって異なりますが、分離することは一貫した原則です。コンポーネント内でデータを取得せず、データレイヤーとコンポーザブルを経由してください。
3. 実装の順序(下から上へ)
依存関係が最も少ないレイヤーから上へと実装し、各レイヤーがその下のレイヤーを使ってコンパイルできるようにします。
- データアクセス - 型/モデルとサービスメソッド。
- コンポーザブル - ビュー向けの読み込みと状態をまとめます。
- プレゼンテーション用コンポーネント - 各ブロックに 1 つずつ作成し、機能バレルを用意します。
- ビュー - コンポーザブルをコンポーネントに接続します。
- ルート - ビューを登録します。
- 横断的要素 - ユーティリティ/フォーマッター、アイコン、i18n キー。
4. 構成とバレル
- 機能のコンポーネントを 1 つのフォルダーにまとめます。例:
component/<domain>/<feature>/。共有する形状を定義するtypes.tsと、そのフォルダーを再エクスポートするindex.tsバレルを用意します。 - ドメイン内で共有するコンポーネントは、機能サブフォルダーの隣にある
component/<domain>/直下に配置し、common/サブフォルダーには入れません。 機能固有のパーツはcomponent/<domain>/<feature>/サブフォルダーに配置し、本当にアプリ全体で共通するコンポーネントはトップレベルのcomponent/common/に配置します。 - ドメイン内で共有するコンポーネントは、それぞれのパス(
@/component/<domain>/<Name>.vue)からインポートしてください。 ドメインのバレル経由ではインポートしません。バレルはそれをインポートする機能を再エクスポートするため、バレル経由にするとインポートの循環が発生します。 - パスエイリアス(
@/component/...、@/composableなど)を使ってインポートしてください。 深い相対パスは使いません。相対インポートは、同じ機能内で同じ場所に配置された子ファイルに限って使用します。 - 新しいファイルを作成したら、すぐにすべてバレルに追加してください。 そうすることで、アプリの他の部分が常に安定した場所からインポートできます。
component/
common/ app-generic components (AppIcon, layout, ...)
<domain>/
<Domain>Badge.vue shared directly across the domain's features
index.ts barrel: shared pieces + the feature barrels
<feature>/ one feature's presentational pieces
types.ts shapes shared within the feature
index.ts barrel: re-exports the folder
5. ビューではなく、合成可能な形でデータを読み込む
- フェッチ、ローディング/エラー処理、デバウンス/競合ガードを
use<Feature>コンポーザブル内にまとめます。ビューはそれを利用し({ data, isLoading, isEmpty, error, reload })、 データ取得の細かな処理ではなく、構成に専念します。 - ローディングだけでなく、空状態とエラー状態も公開します。 フェッチ中に
isEmptyが一瞬表示されないように導出し(loaded && !isLoading && count === 0)、 状態はローディング中のスケルトン、エラー、空状態、データの順に表示します。 - ビュー内で
watch+fetch+手動のデバウンスを独自に実装しないでください。まずは プロジェクトに既存のデータヘルパー(リスト/テーブル用またはレポート用の コンポーザブル、あるいはデータフェッチライブラリ)を使い、それに最も近いものを参考に 新しいものを作成してください。既存のものがない場合(グリーンフィールド)は、 競合ガード付きの小さな基本機能(フェッチ + ローディング + エラー + 古いレスポンスを 破棄するリクエストトークン)を1つ作成し、各機能のコンポーザブルはそれを参考にします。 - プロジェクトで
@basmilius/commonを使っている場合、useDataTable/useDataReportがそのデータヘルパーです。これらは@basmilius/http-clientのサービスと DTO を利用します(両方についてはbasmiliusスキルを参照してください)。
6. 共有状態/モジュールレベルの状態をリセットする
- コンポーザブルまたはストアが、子ルート/オーバーレイルートと共有される
モジュールレベル(シングルトン)の状態にリストを保持する場合は、
reset()を 用意し、リストビューのonUnmountedで呼び出します(またはコンテキストキーをwatchして変更時にリセットします)。 - そうしないと、ページに戻ったときに古い状態や、場合によっては重い状態が同期的に 再描画されてから再読み込みされるため、スピナーが表示されないまま UI がフリーズする ことがあります。
- コンポーネントスコープのコンポーザブル状態に
reset()は不要です。 ビューが インスタンス化するコンポーザブル内で作られた状態は、ビューとともに破棄されます。 対象となるのは、ビューよりも長く存続するモジュールレベル(シングルトン)の状態、または ストアの状態のみです。 reset()によって再フェッチを起こしてはいけません。 コンポーザブルがフィルター入力をwatchして再フェッチする場合、破棄中にreset()で入力をクリアすると、古い フェッチがスケジュールされます。フェッチを再実行せずに、入力とキャッシュデータを まとめてリセットしてください。
7. ルーターとルート
- 画面ごとにルートを1つ作り、ビューは動的な
import()で遅延読み込みします。 - 横断的な関心事(ガード、権限、タイトル)はルートの
metaに置き、各ビュー内で 個別に確認するのではなく、ナビゲーションガードで参照します。 - ディープリンクや戻る操作が必要なモーダル/オーバーレイ画面は、ルートとしてモデル化します。
リストを下にマウントしたままにするため、オーバーレイはリストの子ルートにし
(そのため、機能から離れるときにのみ
reset()が呼び出されます。セクション6を参照)、 ネストされた<RouterView>を通じて描画します。ルートパラメーターはuseRoute()ではなく props(props: true)として渡し、終了時はrouter.back()ではなく親ルートへ移動 します。これにより、直接開いたディープリンクでも終了できます。 - 権限/ガードの詳細はプロジェクトによって異なります(リポジトリの
CLAUDE.mdに従ってください)。基本パターンは、metaで宣言し、ガードで適用することです。
8. 横断的な事項
- アイコンはプロジェクトの中央アイコンレジストリに登録し、アイコン名の値には
stringではなく、プロジェクトのアイコン名型を指定します。 - 共有フォーマッターは
util/に置いて(バレルエクスポートし)、テンプレート内にIntlや書式設定のロジックを直接記述するのではなく、プロジェクトのユーティリティ ライブラリを再利用します。 - ユーザーに表示するテキストはプロジェクトの i18n レイヤーを通し、提供するすべての ロケールにキーを用意します。
関連スキル
code-style- この機能で追加する各.vueファイルの作成方法です。flux-ui- プロジェクトで Flux を使っている場合の、コンポーネントの選択と組み合わせ方です。basmilius- プロジェクトで@basmilius/*パッケージ(Bas Milius 製)を使っている場合、このスキルではプロジェクトごとに異なる具体的なデータレイヤー (DTO サービス)と状態/コンポーザブルレイヤー(useDataTable、ストア)について説明します。- リポジトリの
CLAUDE.md/AGENTS.md- 状態/データライブラリ、権限モデル、 i18n の配置場所、その他のプロジェクト固有のルールです。
コメント
Markdown 対応