# 機能設計書 31-useModel()

## 概要

本ドキュメントは、Vue 3の`useModel()`フック（Composition API）の機能設計を記載する。`useModel()`は`defineModel()`マクロのランタイム実装であり、v-modelバインディングを統一的に扱うためのフックである。

### 本機能の処理概要

`useModel()`は、コンポーネント内でv-modelバインディングを簡潔かつ型安全に扱うためのComposition APIフックである。親コンポーネントからのv-modelバインディングと、ローカル状態の両方を透過的に管理し、双方向データバインディングの実装を大幅に簡素化する。

**業務上の目的・背景**：Vue 3では、コンポーネント間の双方向データバインディングにおいて、従来はpropsの受け取りとemitの発行を個別に実装する必要があった。`useModel()`は`defineModel()`マクロと連携し、この実装パターンを1つのリアクティブな参照にまとめることで、開発者の負担を大幅に軽減する。

**機能の利用シーン**：`<script setup>`構文において`defineModel()`マクロを使用すると、コンパイル時に内部的に`useModel()`に変換される。フォーム入力コンポーネント、カスタム入力コンポーネント、モーダルの表示制御など、親子間の双方向データバインディングが必要なあらゆる場面で利用される。

**主要な処理内容**：
1. コンポーネントインスタンスの取得とpropの存在検証
2. `customRef`を用いたリアクティブ参照の生成
3. `watchSyncEffect`によるprop値の同期的な監視と追跡
4. getterでの値取得時のトランスフォーム（`options.get`）適用
5. setterでの値設定時のトランスフォーム（`options.set`）適用と`update:modelValue`イベント発行
6. v-modelの有無に応じたローカル更新とemitの使い分け
7. `Symbol.iterator`の実装によるデストラクチャリング対応（`[modelValue, modifiers]`形式）
8. モデル修飾子（modifiers）の取得と公開

**関連システム・外部連携**：`@vue/reactivity`パッケージの`customRef`、`ref`と連携。`@vue/compiler-sfc`パッケージの`defineModel()`マクロのコンパイル結果から呼び出される。

**権限による制御**：特になし。

## 関連画面

画面機能マッピングに該当する画面関連情報なし。`useModel()`はランタイムAPIとしてフレームワーク内部で使用される。

## 機能種別

データバインディング / リアクティブ状態管理

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| props | `Record<string, any>` | Yes | コンポーネントのpropsオブジェクト | - |
| name | `string` | Yes | モデルのプロパティ名（例: `modelValue`） | propsOptionsに宣言されていない場合は警告 |
| options | `DefineModelOptions` | No | get/set変換関数を含むオプション | デフォルト: `EMPTY_OBJ` |

### 入力データソース

- 親コンポーネントから渡されるprops（v-model経由）
- コンパイル済み`defineModel()`マクロからの呼び出し

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| ModelRef | `Ref & Iterable` | リアクティブな参照オブジェクト。読み書き可能で、イテレータプロトコルを実装 |

### 出力先

- コンポーネント内部のリアクティブ状態として使用
- setterが呼ばれた際に`update:{name}`イベントとして親コンポーネントに通知

## 処理フロー

### 処理シーケンス

```
1. getCurrentInstance()でコンポーネントインスタンス取得
   └─ インスタンスが存在しない場合は警告を出し空のrefを返却
2. prop名のキャメライズとハイフネーション
   └─ camelize()、hyphenate()でそれぞれの形式に変換
3. propsOptionsに対するprop宣言の検証
   └─ 未宣言の場合は開発モードで警告
4. getModelModifiers()でモデル修飾子を取得
   └─ modelValueの場合はmodelModifiers、それ以外は{name}Modifiers
5. customRef()によるリアクティブ参照の生成
   ├─ watchSyncEffect()でprop値の同期監視を設定
   ├─ get(): track()を呼び出し、options.getがあれば変換して返却
   └─ set(): options.setで変換、v-modelの有無を判定、emit発行
6. Symbol.iteratorの実装
   └─ [ref, modifiers]のデストラクチャリング対応
7. resを返却
```

### フローチャート

```mermaid
flowchart TD
    A[useModel呼び出し] --> B{インスタンスあり?}
    B -->|No| C[警告 + 空ref返却]
    B -->|Yes| D[prop名正規化]
    D --> E{prop宣言済み?}
    E -->|No| F[警告 + 空ref返却]
    E -->|Yes| G[修飾子取得]
    G --> H[customRef生成]
    H --> I[watchSyncEffect設定]
    I --> J[getter/setter定義]
    J --> K[Symbol.iterator実装]
    K --> L[ModelRef返却]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-31-01 | ローカル更新判定 | 親がv-modelを渡していない場合、setterはローカル値を直接更新しtriggerを呼ぶ | rawPropsに対応するpropとupdateハンドラが存在しない場合 |
| BR-31-02 | 値変更スキップ | emitされる値がlocalValueと同じであり、かつ前回のsetValueとも変わっていない場合はsetをスキップ | hasChanged()で比較 |
| BR-31-03 | 同期トリガー | setterで変換後の値と変換前の値が異なり、かつ前回emitされた値と変換後の値が同じ場合、親が更新をトリガーしないため強制的にtriggerを呼ぶ | options.setによる値変換がある場合 |

### 計算ロジック

修飾子の取得ロジック:
- `modelValue`または`model-value`の場合: `props.modelModifiers`を参照
- それ以外: `props[${name}Modifiers]`、`props[${camelize(name)}Modifiers]`、`props[${hyphenate(name)}Modifiers]`の順に検索

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

該当なし（フロントエンドフレームワークのランタイムAPIのため）

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | 開発時警告 | アクティブなコンポーネントインスタンスなしで呼び出し | `useModel() called without active instance.` |
| - | 開発時警告 | propsOptionsに宣言されていないprop名を指定 | `useModel() called with prop "${name}" which is not declared.` |

### リトライ仕様

該当なし。

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

該当なし。

## パフォーマンス要件

- `watchSyncEffect`により、prop値の変更は同期的に検出・反映される
- `customRef`の使用により、不要な再レンダリングが抑制される（`hasChanged`による変更検出）
- `withModifiers`のキャッシュ機構と同様、同一のmodifiersに対する重複処理を回避

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

特記事項なし。フロントエンドフレームワークの内部APIであり、直接的なセキュリティリスクは存在しない。

## 備考

- Vue 3.4で導入された`defineModel()`マクロの内部実装として使用される
- `Symbol.iterator`の実装により、`const [model, modifiers] = defineModel()`のようなデストラクチャリング構文が可能
- `#10279`の修正により、setter経由で変換された値が親に反映されない場合の同期問題が解決されている

---

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

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

### 推奨読解順序

#### Step 1: データ構造を理解する

まず、`useModel()`が返すModelRefの型定義と関連するオプション型を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | apiSetupHelpers.ts | `packages/runtime-core/src/apiSetupHelpers.ts` | `DefineModelOptions`型と`ModelRef`型の定義を確認 |

**読解のコツ**: `ModelRef`は`Ref`を拡張した型であり、`Symbol.iterator`による分割代入をサポートする点が特徴的である。

#### Step 2: エントリーポイントを理解する

`useModel()`関数本体の構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | useModel.ts | `packages/runtime-core/src/helpers/useModel.ts` | `useModel()`関数の全体構造 |

**主要処理フロー**:
1. **25-28行目**: `getCurrentInstance()`によるインスタンス取得と検証
2. **31行目**: prop名のキャメライズ
3. **32-35行目**: propsOptionsでのprop宣言存在チェック
4. **37-38行目**: ハイフネーションと修飾子の取得
5. **40-101行目**: `customRef`による本体実装
6. **45-51行目**: `watchSyncEffect`によるprop値の同期監視
7. **54-56行目**: getter - `track()`と`options.get`変換
8. **59-98行目**: setter - 値変換、v-model有無判定、emit発行
9. **104-115行目**: `Symbol.iterator`の実装

#### Step 3: 修飾子取得ロジックを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | useModel.ts | `packages/runtime-core/src/helpers/useModel.ts` | `getModelModifiers()`関数（120-129行目） |

**主要処理フロー**:
- **124行目**: `modelValue`の場合は`props.modelModifiers`を返す
- **126-128行目**: それ以外はキャメライズ・ハイフネーション形式で検索

#### Step 4: コンパイラとの連携を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | defineModel.ts | `packages/compiler-sfc/src/script/defineModel.ts` | `defineModel()`マクロがコンパイル時にどのように`useModel()`呼び出しに変換されるかを確認 |

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

```
defineModel()マクロ（コンパイル時変換）
    │
    └─ useModel(props, name, options)
           │
           ├─ getCurrentInstance()
           ├─ camelize(name) / hyphenate(name)
           ├─ getModelModifiers(props, camelizedName)
           ├─ customRef((track, trigger) => {...})
           │      ├─ watchSyncEffect(() => { ... })  ← prop値の同期監視
           │      ├─ get()  ← track() + options.get
           │      └─ set()  ← options.set + hasChanged + emit
           └─ Symbol.iterator 実装
```

### データフロー図

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

親コンポーネント     ┌──────────────────────────┐
  v-model ─────────▶│ useModel()               │──────▶ ModelRef（リアクティブ参照）
                    │  ├ customRef               │
  props[name] ─────▶│  │ ├ watchSyncEffect      │
                    │  │ │  └ prop値同期          │
                    │  │ ├ get → options.get      │
                    │  │ └ set → options.set      │
                    │  │    └ emit('update:name') │──────▶ update:name イベント
                    │  └ Symbol.iterator          │──────▶ [ref, modifiers]
                    └──────────────────────────┘
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| useModel.ts | `packages/runtime-core/src/helpers/useModel.ts` | ソース | useModel()のメイン実装 |
| apiSetupHelpers.ts | `packages/runtime-core/src/apiSetupHelpers.ts` | ソース | DefineModelOptions, ModelRef型定義 |
| defineModel.ts | `packages/compiler-sfc/src/script/defineModel.ts` | ソース | defineModel()マクロのコンパイラ実装 |
| index.ts | `packages/runtime-core/src/index.ts` | ソース | useModelのエクスポート |
| componentEmits.ts | `packages/runtime-core/src/componentEmits.ts` | ソース | emit機構（update:nameイベント） |
| useModel.spec.ts | `packages/runtime-core/__tests__/helpers/useModel.spec.ts` | テスト | useModel()のユニットテスト |
