# 機能設計書 32-v-model

## 概要

本ドキュメントは、Vue 3の組み込みディレクティブ`v-model`のランタイム実装の機能設計を記載する。`v-model`はフォーム要素（input, textarea, select）への双方向データバインディングを提供し、要素タイプに応じた5つの個別実装（vModelText, vModelCheckbox, vModelRadio, vModelSelect, vModelDynamic）で構成される。

### 本機能の処理概要

`v-model`ディレクティブは、HTMLフォーム要素とVueのリアクティブデータ間の双方向バインディングを実現する。テンプレートコンパイラが要素タイプに応じて適切なディレクティブ実装を選択し、VNodeフックとして適用する。

**業務上の目的・背景**：フォーム入力はWebアプリケーションの根幹をなす操作であり、ユーザー入力とアプリケーション状態の同期を自動化することで、開発者はボイラープレートコードを書くことなく、リアクティブなフォームを構築できる。各フォーム要素タイプ（テキスト、チェックボックス、ラジオボタン、セレクト）の入力イベントの違いを抽象化し、統一的なインターフェースを提供する。

**機能の利用シーン**：テキスト入力フォーム、チェックボックスによる複数選択、ラジオボタンによる単一選択、セレクトボックスによる選択、動的に要素タイプが変わるフォームなど、あらゆるフォーム入力シーンで使用される。

**主要な処理内容**：
1. `vModelText`: テキスト入力要素（input[type=text], textarea）に対して`input`/`change`イベントで値を同期
2. `vModelCheckbox`: チェックボックスに対して配列/Set/ブーリアン値を管理
3. `vModelRadio`: ラジオボタンに対して`looseEqual`で値を比較・同期
4. `vModelSelect`: セレクト要素に対して単一/複数選択を管理
5. `vModelDynamic`: 動的要素タイプに応じて上記4つを切り替え
6. IME（Input Method Editor）合成イベントへの対応
7. `trim`/`number`/`lazy`修飾子のサポート
8. SSR向けのgetSSRProps実装

**関連システム・外部連携**：`@vue/runtime-core`のディレクティブフック機構と連携。`@vue/compiler-dom`のv-model変換処理により、テンプレートから適切なディレクティブが選択される。

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

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 1 | App（メイン画面） | 補助機能 | autoSave設定の双方向バインディング（:model-value経由） |

## 機能種別

データバインディング / ディレクティブ

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| value | `any` | Yes | バインドする値 | - |
| modifiers.lazy | `boolean` | No | changeイベントで同期（inputの代わり） | vModelTextのみ |
| modifiers.trim | `boolean` | No | 入力値のトリム | vModelTextのみ |
| modifiers.number | `boolean` | No | 入力値の数値変換 | vModelText, vModelSelectのみ |

### 入力データソース

- コンポーネントのリアクティブデータ（ref, reactive等）
- テンプレート内のv-modelバインディング式

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| onUpdate:modelValue | `Function` | 値変更時に呼ばれるコールバック。親コンポーネントへの値伝播 |
| DOM要素の値 | `string/boolean` | フォーム要素のvalue/checked属性の更新 |

### 出力先

- DOM要素のvalue/checked属性
- 親コンポーネントのリアクティブデータ（`onUpdate:modelValue`イベント経由）

## 処理フロー

### 処理シーケンス

```
1. created フック
   ├─ getModelAssigner()でonUpdate:modelValueハンドラを取得
   ├─ イベントリスナーを登録（input/change）
   └─ IME合成イベントリスナーを登録（lazy=false時）
2. mounted フック
   └─ DOM要素に初期値を設定
3. beforeUpdate フック
   ├─ getModelAssigner()を更新
   ├─ composing中は処理をスキップ
   ├─ 要素の現在値と新しい値を比較
   └─ 異なる場合のみDOM要素を更新
```

### フローチャート

```mermaid
flowchart TD
    A[v-model ディレクティブ適用] --> B{要素タイプ判定}
    B -->|input text/textarea| C[vModelText]
    B -->|input checkbox| D[vModelCheckbox]
    B -->|input radio| E[vModelRadio]
    B -->|select| F[vModelSelect]
    B -->|動的| G[vModelDynamic]
    C --> H[created: input/changeリスナー登録]
    H --> I{lazy修飾子?}
    I -->|Yes| J[changeイベントで同期]
    I -->|No| K[inputイベントで同期 + IME対応]
    K --> L[mounted: 初期値設定]
    L --> M[beforeUpdate: 値更新]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-32-01 | IME合成中スキップ | composingフラグがtrueの間、inputイベントによる値同期をスキップ | vModelText, lazy=false時 |
| BR-32-02 | アクティブ要素スキップ | document.activeElementが対象要素で、type!='range'の場合、不要な更新をスキップ | vModelText, beforeUpdate時 |
| BR-32-03 | チェックボックス配列管理 | modelValueが配列の場合、checked時にconcatで追加、unchecked時にspliceで削除 | vModelCheckbox |
| BR-32-04 | チェックボックスSet管理 | modelValueがSetの場合、checked時にadd、unchecked時にdelete | vModelCheckbox |
| BR-32-05 | セレクト複数選択 | multipleの場合、配列またはSetで選択値を管理 | vModelSelect |
| BR-32-06 | _assigning非同期制御 | selectのchangeイベント処理後、nextTickまで_assigningフラグを設定し再同期を防止 | vModelSelect |

### 計算ロジック

`castValue`関数: trim修飾子でvalue.trim()、number修飾子でlooseToNumber()を適用。

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

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

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | 開発時警告 | select multipleでmodelValueが配列/Setでない | `<select multiple v-model> expects an Array or Set value` |

### リトライ仕様

該当なし。

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

該当なし。

## パフォーマンス要件

- VNodeフックとしてtree-shaking可能な構造で実装されており、使用しないディレクティブはバンドルから除外される
- `beforeUpdate`で`elValue === newValue`の比較により不要なDOM操作を回避
- `looseToNumber`の適用を`/^0\d/`パターンチェックで制御し、0始まりの数値文字列の不正変換を防止

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

特記事項なし。入力値の検証はアプリケーション側で実施する必要がある。

## 備考

- `_value`プロパティ: `:value`バインディングで設定された元の値を保持（HTMLのvalue属性は常に文字列化されるため）
- `_trueValue`/`_falseValue`プロパティ: チェックボックスの`:true-value`/`:false-value`バインディング用
- `_modelValue`プロパティ: チェックボックスのchangeリスナーからmodelValueにアクセスするため
- Vue 2互換モード（`__COMPAT__`）で`onModelCompat:input`イベントにも対応

---

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

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

### 推奨読解順序

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

`ModelDirective`型と`AssignerFn`型を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | vModel.ts | `packages/runtime-dom/src/directives/vModel.ts` | `AssignerFn`型（19行目）、`ModelDirective`型（42-46行目）の定義 |

**読解のコツ**: `ModelDirective`は`ObjectDirective`を拡張し、要素に`assignKey`シンボルで代入関数を保持する構造。各ディレクティブはVNodeフック（created, mounted, beforeUpdate, updated）を実装する。

#### Step 2: テキスト入力のvModelTextを理解する

もっとも基本的なvModelTextから読む。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | vModel.ts | `packages/runtime-dom/src/directives/vModel.ts` | `vModelText`実装（56-117行目） |

**主要処理フロー**:
1. **60-81行目**: createdフック - イベントリスナー登録、IME合成対応
2. **84-86行目**: mountedフック - 初期値設定
3. **87-116行目**: beforeUpdateフック - 値比較と更新

#### Step 3: チェックボックスのvModelCheckboxを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | vModel.ts | `packages/runtime-dom/src/directives/vModel.ts` | `vModelCheckbox`実装（119-158行目）、`setChecked`関数（160-183行目） |

**主要処理フロー**:
- **129-138行目**: 配列の場合のconcact/splice処理
- **139-146行目**: Setの場合のadd/delete処理
- **148行目**: 単一値の場合のtrue-value/false-value処理

#### Step 4: ラジオ・セレクト・ダイナミックを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | vModel.ts | `packages/runtime-dom/src/directives/vModel.ts` | `vModelRadio`（185-199行目）、`vModelSelect`（201-239行目）、`vModelDynamic`（292-307行目） |

#### Step 5: SSR対応を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | vModel.ts | `packages/runtime-dom/src/directives/vModel.ts` | `initVModelForSSR()`（344-380行目） |

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

```
テンプレートコンパイラ（v-model変換）
    │
    ├─ vModelText（input[text] / textarea）
    │      ├─ getModelAssigner()
    │      ├─ addEventListener(input/change)
    │      ├─ onCompositionStart / onCompositionEnd
    │      └─ castValue(trim, number)
    │
    ├─ vModelCheckbox（input[checkbox]）
    │      ├─ getModelAssigner()
    │      ├─ addEventListener(change)
    │      ├─ setChecked()
    │      ├─ getValue() / getCheckboxValue()
    │      └─ looseIndexOf() / looseEqual()
    │
    ├─ vModelRadio（input[radio]）
    │      ├─ getModelAssigner()
    │      ├─ addEventListener(change)
    │      └─ looseEqual()
    │
    ├─ vModelSelect（select）
    │      ├─ getModelAssigner()
    │      ├─ addEventListener(change)
    │      ├─ setSelected()
    │      └─ nextTick()
    │
    └─ vModelDynamic（動的要素タイプ）
           ├─ resolveDynamicModel()
           └─ callModelHook()
```

### データフロー図

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

ユーザー入力          ┌──────────────────────────┐
  (キーボード) ──────▶│ DOMイベント               │
                    │  ├ input/change             │
                    │  └ compositionstart/end     │
                    │         │                   │
                    │         ▼                   │
                    │  castValue(trim, number)    │
                    │         │                   │
                    │         ▼                   │
                    │  assignFn(value)            │──────▶ onUpdate:modelValue
                    │                             │         （親のリアクティブデータ更新）
                    └──────────────────────────┘

リアクティブデータ    ┌──────────────────────────┐
  変更通知 ──────────▶│ beforeUpdate フック        │
                    │  └ el.value = newValue     │──────▶ DOM要素の値更新
                    └──────────────────────────┘
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| vModel.ts | `packages/runtime-dom/src/directives/vModel.ts` | ソース | v-modelディレクティブのメイン実装 |
| events.ts | `packages/runtime-dom/src/modules/events.ts` | ソース | addEventListenerヘルパー |
| index.ts | `packages/runtime-dom/src/index.ts` | ソース | ディレクティブのエクスポート |
| runtimeHelpers.ts | `packages/compiler-dom/src/runtimeHelpers.ts` | ソース | コンパイラからのランタイムヘルパー参照 |
| vModel.spec.ts | `packages/runtime-dom/__tests__/directives/vModel.spec.ts` | テスト | v-modelディレクティブのテスト |
