# 機能設計書 16-defineSlots() / defineModel()

## 概要

本ドキュメントは、スロット型定義とv-modelバインディングを定義するコンパイラマクロ `defineSlots()` と `defineModel()` の機能設計書である。

### 本機能の処理概要

`defineSlots()` は `<script setup>` 内でコンポーネントのスロット型を宣言するマクロである。型安全なスロット定義を提供し、コンパイル時に `useSlots()` に変換される。

`defineModel()` は `<script setup>` 内で `v-model` バインディングに対応するPropと `update:` イベントを同時に定義するマクロである。双方向バインディングをリアクティブなRef値として扱えるようにし、コンパイル時に `useModel()` ヘルパーに変換される。

**業務上の目的・背景**：スロットの型安全性を確保することで、親コンポーネントから渡されるスロットコンテンツの型チェックをコンパイル時に行える。また、`defineModel()` は従来のProps + Emits による `v-model` 実装パターンのボイラープレートを大幅に削減し、双方向バインディングの定義を1行で完結させる。

**機能の利用シーン**：
- `defineSlots()`: ジェネリックコンポーネントやスコープ付きスロットの型定義
- `defineModel()`: フォーム入力コンポーネント、モーダルダイアログの開閉制御など、v-modelを受け付けるコンポーネント

**主要な処理内容**：
1. **defineSlots()のコンパイル時処理**：
   - 重複呼び出しチェック
   - 引数がある場合のエラーチェック
   - 変数宣言がある場合は `useSlots()` に変換
2. **defineModel()のコンパイル時処理**：
   - モデル名の解決（デフォルトは`modelValue`）
   - 重複モデル名チェック
   - `get`/`set` オプションの分離処理
   - `useModel(__props, modelName, options)` への変換
   - PropとEmit（`update:modelName`）の自動生成

**関連システム・外部連携**：`@vue/compiler-sfc` パッケージでコンパイル時の変換が行われる。`useModel()` ヘルパーは `@vue/runtime-core` に実装されている。

**権限による制御**：権限による制御は存在しない。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 3 | VersionSelect（バージョン選択） | 主機能 | defineModel()によるversion値のv-modelバインディング定義 |

## 機能種別

コンパイラマクロ / 型定義ヘルパー / 双方向バインディング

## 入力仕様

### defineSlots() の入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| （なし） | - | - | 引数は受け付けない | 引数がある場合はエラー |
| S（型パラメータ） | Record\<string, any\> | No | スロット型定義 | - |

### defineModel() の入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| name | string | No | モデル名。省略時は`modelValue` | 重複チェック |
| options | PropOptions & DefineModelOptions | No | Propオプション + get/setアクセサ | - |
| T（型パラメータ） | any | No | モデル値の型 | - |
| M（型パラメータ） | PropertyKey | No | モディファイア型 | - |

### 入力データソース

`<script setup>` ブロック内で開発者が直接呼び出す（コンパイラマクロ）。

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| defineSlots戻り値 | StrictUnwrapSlotsType\<SlotsType\<S\>\> | 型付きslotsオブジェクト |
| defineModel戻り値 | ModelRef\<T, M, G, S\> | リアクティブなRef（v-modelバインディング） |

### 出力先

defineSlots: コンパイル後に `useSlots()` に変換。defineModel: コンパイル後に `useModel()` に変換され、Propsと `update:` Emitが自動生成される。

## 処理フロー

### defineSlots() の処理シーケンス

```
1. isCallOf(node, 'defineSlots') で呼び出し検出
2. 重複呼び出しチェック
3. 引数があればエラー
4. 変数宣言（declId）がある場合: node を useSlots() に置換
```

### defineModel() の処理シーケンス

```
1. isCallOf(node, 'defineModel') で呼び出し検出
2. ctx.hasDefineModelCall = true
3. モデル名の解決
   ├─ 第1引数がStringLiteralの場合: その値をモデル名とする
   └─ それ以外: 'modelValue' をモデル名とする
4. 重複モデル名チェック
5. オプション処理
   ├─ get/set: ランタイムオプションとして保持
   └─ その他（type, required, default等）: Propオプションとして分離
6. バインディングメタデータ登録（BindingTypes.PROPS）
7. callee を useModel に置換
8. 引数に __props とモデル名を挿入
```

### フローチャート

```mermaid
flowchart TD
    A[defineModel呼び出し検出] --> B[hasDefineModelCall = true]
    B --> C{第1引数がStringLiteral?}
    C -->|Yes| D[modelName = 引数の値]
    C -->|No| E[modelName = 'modelValue']
    D --> F{重複モデル名?}
    E --> F
    F -->|Yes| G[エラー]
    F -->|No| H[get/setオプション分離]
    H --> I[ctx.modelDecls に登録]
    I --> J[callee → useModel に置換]
    J --> K[引数に __props, modelName 挿入]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-16-01 | defineSlots引数禁止 | defineSlots()は引数を受け付けない | コンパイル時 |
| BR-16-02 | defineSlots単一呼び出し | defineSlots()は1回のみ呼び出し可能 | コンパイル時 |
| BR-16-03 | defineModelデフォルト名 | モデル名未指定時は'modelValue'が使用される | モデル名引数なしの場合 |
| BR-16-04 | defineModel重複名禁止 | 同名のモデルは2回定義できない | コンパイル時 |
| BR-16-05 | defineModel自動変換 | defineModel() は useModel(__props, name, options) に変換される | コンパイル時 |
| BR-16-06 | Emits自動生成 | defineModelのモデル名に対応する `update:modelName` Emitが自動生成される | genRuntimeEmits時 |
| BR-16-07 | モディファイアProp | `modelModifiers` または `{name}Modifiers` Propが自動生成される | genModelProps時 |

### 計算ロジック

`genModelProps()` はモデル宣言からPropオプション文字列を生成する。TypeScript型情報がある場合は `inferRuntimeType` で型推論を行い、Boolean/Function型の場合はプロダクションビルドでも型情報を保持する。

## データベース操作仕様

該当なし。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | コンパイルエラー | defineSlots()の重複呼び出し | 1回のみに修正 |
| - | コンパイルエラー | defineSlots()への引数指定 | 引数を除去、型パラメータで定義 |
| - | コンパイルエラー | defineModel()の重複モデル名 | 異なる名前を使用 |
| - | ランタイム警告 | `<script setup>` 外での呼び出し | `<script setup>` 内で使用する |

### リトライ仕様

該当なし。

## トランザクション仕様

該当なし。

## パフォーマンス要件

特になし。

## セキュリティ考慮事項

特になし。

## 備考

- `ModelRef` 型は `Ref<G, S>` を拡張し、モディファイア情報をタプルとして保持する。
- `defineModel()` は Vue 3.4 で安定版として導入された。
- 親コンポーネントが `v-model` を渡していない場合、`defineModel()` のRef値はローカルRefとして動作する。

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: ランタイム型定義を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | apiSetupHelpers.ts | `packages/runtime-core/src/apiSetupHelpers.ts` | defineSlots関数のランタイムスタブ（234-241行目） |
| 1-2 | apiSetupHelpers.ts | `packages/runtime-core/src/apiSetupHelpers.ts` | ModelRef型, DefineModelOptions型（243-252行目） |
| 1-3 | apiSetupHelpers.ts | `packages/runtime-core/src/apiSetupHelpers.ts` | defineModel関数のランタイムスタブ（287-313行目） |

**読解のコツ**: `ModelRef` 型は `Ref<G, S>` と `[ModelRef<T, M, G, S>, Record<M, true | undefined>]` のintersection型で、Refとしてもタプルとしても使える。

#### Step 2: コンパイラ側のdefineSlots処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | defineSlots.ts | `packages/compiler-sfc/src/script/defineSlots.ts` | processDefineSlots関数（7-33行目） |

**主要処理フロー**:
- **12行目**: `isCallOf(node, DEFINE_SLOTS)` で検出
- **16-18行目**: 重複チェック
- **20-22行目**: 引数チェック
- **25-29行目**: 変数宣言がある場合は `useSlots()` に置換

#### Step 3: コンパイラ側のdefineModel処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | defineModel.ts | `packages/compiler-sfc/src/script/defineModel.ts` | ModelDeclインターフェース（9-14行目） |
| 3-2 | defineModel.ts | `packages/compiler-sfc/src/script/defineModel.ts` | processDefineModel関数（16-115行目） |
| 3-3 | defineModel.ts | `packages/compiler-sfc/src/script/defineModel.ts` | genModelProps関数（117-176行目） |

**主要処理フロー**:
- **27-28行目**: 型パラメータの取得
- **31-39行目**: モデル名の解決（StringLiteral判定）
- **41-43行目**: 重複モデル名チェック
- **49-86行目**: get/setオプションとPropオプションの分離
- **88-96行目**: ctx.modelDecls への登録、バインディングメタデータ登録
- **99-103行目**: callee を `useModel` に置換
- **105-112行目**: 引数 `__props, modelName` の挿入

### プログラム呼び出し階層図

```
compileScript(sfc, options)
    │
    ├─ processDefineSlots(ctx, node, declId)
    │      └─ ctx.s.overwrite() → useSlots()
    │
    ├─ processDefineModel(ctx, node, declId)
    │      ├─ モデル名解決
    │      ├─ get/setオプション分離
    │      ├─ ctx.modelDecls[modelName] 設定
    │      ├─ ctx.s.overwrite() → useModel
    │      └─ ctx.s.appendLeft() → __props, modelName 挿入
    │
    └─ genModelProps(ctx)
           ├─ inferRuntimeType() → 型推論
           └─ モディファイアProp生成
```

### データフロー図

```
[入力]                              [処理]                       [出力]

defineSlots<{ default(p): any }>  ▶ processDefineSlots()      ▶ useSlots()
defineModel<string>('name')       ▶ processDefineModel()      ▶ useModel(__props, 'name')
                                  ▶ genModelProps()            ▶ props: { name: {}, nameModifiers: {} }
                                  ▶ genRuntimeEmits()          ▶ emits: ['update:name']
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| apiSetupHelpers.ts | `packages/runtime-core/src/apiSetupHelpers.ts` | ソース | defineSlots/defineModelのランタイムスタブ |
| defineSlots.ts | `packages/compiler-sfc/src/script/defineSlots.ts` | ソース | defineSlotsのコンパイラ処理 |
| defineModel.ts | `packages/compiler-sfc/src/script/defineModel.ts` | ソース | defineModelのコンパイラ処理 |
| useModel.ts | `packages/runtime-core/src/helpers/useModel.ts` | ソース | useModelヘルパーの実装 |
| context.ts | `packages/compiler-sfc/src/script/context.ts` | ソース | ScriptCompileContext |
| resolveType.ts | `packages/compiler-sfc/src/script/resolveType.ts` | ソース | 型推論 |
