# 機能設計書 17-withDefaults()

## 概要

本ドキュメントは、`defineProps()` で定義したPropsにデフォルト値を設定するヘルパーマクロ `withDefaults()` の機能設計書である。

### 本機能の処理概要

`withDefaults()` は、型ベースの `defineProps()` で宣言されたPropsにデフォルト値を提供するコンパイラマクロである。型ベースの `defineProps<T>()` はランタイム引数を取れないため、デフォルト値の指定に `withDefaults()` が使用される。コンパイル時にデフォルト値がPropオプションの `default` フィールドに変換される。

**業務上の目的・背景**：TypeScript型ベースのProp定義において、デフォルト値を型安全に指定する手段を提供する。`withDefaults()` を使用することで、型定義とデフォルト値の型整合性がコンパイル時にチェックされる。

**機能の利用シーン**：型ベースの `defineProps<T>()` を使用しているコンポーネントで、一部のPropsにデフォルト値を設定する場合に使用される。

**主要な処理内容**：
1. **コンパイル時処理（processWithDefaults）**：
   - 第1引数が `defineProps()` 呼び出しであることを検証
   - 型ベースの `defineProps` でのみ使用可能であることを検証
   - デストラクチャ使用時の警告
   - 第2引数（デフォルトオブジェクト）を `ctx.propsRuntimeDefaults` に設定
2. **ランタイムヘルパー（mergeDefaults）**：
   - コンパイル結果として生成されるコードで使用
   - Propオプションにdefaultフィールドをマージ
   - 静的デフォルトの場合は直接インライン化、動的な場合は `mergeDefaults()` を呼び出し

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

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

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | 該当なし | - | withDefaults()は特定の画面に限定されない汎用マクロ |

## 機能種別

コンパイラマクロ / Props デフォルト値設定

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| props | DefineProps\<T, BKeys\> | Yes | defineProps()の戻り値 | defineProps()呼び出しであること |
| defaults | Defaults (InferDefaults\<T\>) | Yes | デフォルト値オブジェクト | 必須（省略するとコンパイルエラー） |

### 入力データソース

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

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| 戻り値 | PropsWithDefaults\<T, Defaults, BKeys\> | デフォルト値が適用されたProps型（デフォルトのあるプロパティはundefinedを除外） |

### 出力先

コンパイル後のコンポーネントオプションの `props` フィールド内のdefault値に展開される。

## 処理フロー

### 処理シーケンス

```
1. processWithDefaults(ctx, node, declId) の呼び出し
   ├─ isCallOf(node, 'withDefaults') で呼び出し検出
   ├─ 第1引数が defineProps() であることを検証（processDefineProps再帰呼び出し）
   ├─ ランタイム宣言のdefinePropsとの組み合わせを拒否
   ├─ デストラクチャとの組み合わせ時に警告
   └─ ctx.propsRuntimeDefaults = node.arguments[1] に設定
2. genRuntimeProps(ctx) でランタイムコード生成
   ├─ hasStaticWithDefaults() で静的デフォルトかチェック
   ├─ 静的な場合: 各Propに直接 default: value をインライン化
   └─ 動的な場合: mergeDefaults(propsDecls, defaultsObject) を生成
```

### フローチャート

```mermaid
flowchart TD
    A[withDefaults呼び出し検出] --> B{第1引数はdefineProps?}
    B -->|No| C[エラー: defineProps必須]
    B -->|Yes| D{型ベースのdefineProps?}
    D -->|No| E[エラー: 型ベースのみ対応]
    D -->|Yes| F{デストラクチャ使用?}
    F -->|Yes| G[警告: 不要]
    F -->|No| H[OK]
    G --> I{第2引数あり?}
    H --> I
    I -->|No| J[エラー: 第2引数必須]
    I -->|Yes| K[ctx.propsRuntimeDefaults設定]
    K --> L[genRuntimeProps]
    L --> M{静的デフォルト?}
    M -->|Yes| N[default直接インライン化]
    M -->|No| O[mergeDefaults生成]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-17-01 | 型ベースdefineProps限定 | withDefaults()は型ベースのdefineProps()とのみ組み合わせ可能 | コンパイル時 |
| BR-17-02 | 第1引数検証 | 第1引数はdefineProps()呼び出しでなければならない | コンパイル時 |
| BR-17-03 | 第2引数必須 | デフォルトオブジェクト（第2引数）は必須 | コンパイル時 |
| BR-17-04 | 静的デフォルト最適化 | デフォルトオブジェクトがスプレッドや計算プロパティを含まないObjectExpressionの場合、各Propに直接default値をインライン化 | コンパイル時 |
| BR-17-05 | デストラクチャ非推奨 | デストラクチャとwithDefaults()の組み合わせは非推奨。デストラクチャのデフォルト値構文を使用すべき | コンパイル時 |
| BR-17-06 | 非プリミティブデフォルト | 配列・オブジェクト型のデフォルト値は関数でラップされる（`() => (value)`） | 生成コードで自動適用 |

### 計算ロジック

`mergeDefaults()` ランタイムヘルパーは以下のロジックでPropsオプションにデフォルト値をマージする：
1. `normalizePropsOrEmits()` でProps定義を正規化
2. 各デフォルトキーに対して:
   - 配列または関数形式のProp: `{ type: opt, default: defaults[key] }` に展開
   - オブジェクト形式のProp: `opt.default = defaults[key]` を設定
   - null（文字列配列形式で宣言されたProp）: `{ default: defaults[key] }` を設定
   - Propが見つからない: 開発モードで警告

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

該当なし。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | コンパイルエラー | 第1引数がdefineProps()でない | withDefaults(defineProps<T>(), {...})の形式で使用 |
| - | コンパイルエラー | ランタイム宣言のdefinePropsと組み合わせた | 型ベースのdefinePropsを使用 |
| - | コンパイルエラー | 第2引数が指定されていない | デフォルト値オブジェクトを指定 |
| - | コンパイル警告 | デストラクチャとwithDefaultsを併用 | デストラクチャのデフォルト値構文を使用 |
| - | ランタイム警告 | デフォルトキーに対応するProp宣言がない | Prop名を確認 |

### リトライ仕様

該当なし。

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

該当なし。

## パフォーマンス要件

- 静的デフォルト（ObjectExpressionで計算プロパティなし）の場合はインライン化により、ランタイムの `mergeDefaults()` 呼び出しを回避する最適化が行われる。

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

特になし。

## 備考

- Vue 3.5以降ではPropsのリアクティブデストラクチャリングが導入され、`const { foo = 1 } = defineProps(...)` 形式でデフォルト値を指定できるようになった。この場合 `withDefaults()` は不要。
- `PropsWithDefaults` 型は、デフォルト値が設定されたプロパティからundefinedを除外する型計算を行う。

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | apiSetupHelpers.ts | `packages/runtime-core/src/apiSetupHelpers.ts` | withDefaults関数のランタイムスタブ（379-391行目） |
| 1-2 | apiSetupHelpers.ts | `packages/runtime-core/src/apiSetupHelpers.ts` | InferDefaults, PropsWithDefaults型（320-357行目） |
| 1-3 | apiSetupHelpers.ts | `packages/runtime-core/src/apiSetupHelpers.ts` | mergeDefaults関数（428-452行目） |

**読解のコツ**: `PropsWithDefaults` 型は、デフォルト値が設定されたキーに対して `-?` 修飾子でundefinedを除外し、`NotUndefined<T[K]>` で型からundefinedを取り除く。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | defineProps.ts | `packages/compiler-sfc/src/script/defineProps.ts` | processWithDefaults関数（95-139行目） |
| 2-2 | defineProps.ts | `packages/compiler-sfc/src/script/defineProps.ts` | hasStaticWithDefaults関数（314-324行目） |
| 2-3 | defineProps.ts | `packages/compiler-sfc/src/script/defineProps.ts` | genRuntimePropFromType関数（240-307行目） |

**主要処理フロー**:
1. **100行目**: `isCallOf(node, WITH_DEFAULTS)` で呼び出し検出
2. **103-109行目**: 第1引数に対して `processDefineProps()` を再帰呼び出し
3. **117-123行目**: ランタイム宣言との組み合わせを拒否
4. **124-131行目**: デストラクチャとの組み合わせ時に警告
5. **132行目**: `ctx.propsRuntimeDefaults = node.arguments[1]`
6. **314-324行目**: 静的デフォルト判定（SpreadElementやcomputed keyがないObjectExpression）

#### Step 3: ランタイムのmergeDefaults処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | apiSetupHelpers.ts | `packages/runtime-core/src/apiSetupHelpers.ts` | mergeDefaults関数（428-452行目） |

**主要処理フロー**:
- **432行目**: `normalizePropsOrEmits(raw)` でProps定義を正規化
- **433-450行目**: 各デフォルトキーに対してdefault値をマージ
- **437-438行目**: 配列/関数形式のPropを `{ type: opt, default: defaults[key] }` に展開
- **440行目**: オブジェクト形式のPropに `opt.default = defaults[key]` を設定
- **447-448行目**: `skipFactory` フラグの処理

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

```
compileScript(sfc, options)
    │
    └─ processDefineProps(ctx, node, declId)
           │
           └─ processWithDefaults(ctx, node, declId)
                  ├─ processDefineProps(ctx, node.arguments[0], declId, true)
                  └─ ctx.propsRuntimeDefaults = node.arguments[1]
                         │
                         └─ genRuntimeProps(ctx)
                                ├─ extractRuntimeProps(ctx)
                                │      ├─ hasStaticWithDefaults(ctx) → 静的判定
                                │      └─ genRuntimePropFromType(ctx, prop, hasStaticDefaults)
                                │             └─ default値をインライン or mergeDefaults
                                └─ mergeDefaults (ランタイムヘルパー)
```

### データフロー図

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

withDefaults(                               processWithDefaults()
  defineProps<{ size?: number }>(),     ▶   extractRuntimeProps()      ▶   props: {
  { size: 3 }                                                               size: { type: Number, default: 3 }
)                                                                          }
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| apiSetupHelpers.ts | `packages/runtime-core/src/apiSetupHelpers.ts` | ソース | withDefaults/mergeDefaultsの定義 |
| defineProps.ts | `packages/compiler-sfc/src/script/defineProps.ts` | ソース | processWithDefaults, hasStaticWithDefaults, genRuntimePropFromType |
| resolveType.ts | `packages/compiler-sfc/src/script/resolveType.ts` | ソース | 型推論（inferRuntimeType） |
| context.ts | `packages/compiler-sfc/src/script/context.ts` | ソース | ScriptCompileContext |
| compileScript.ts | `packages/compiler-sfc/src/compileScript.ts` | ソース | SFCスクリプトコンパイルのエントリーポイント |
