# 通知設計書 8-コンポーネントイベント発行（emit）

## 概要

本ドキュメントは、Vue.jsのコンポーネントイベント発行（`emit`）機構の設計仕様を記述する。`packages/runtime-core/src/componentEmits.ts`に実装されており、子コンポーネントから`emit()`を使って親コンポーネントにカスタムイベントを通知する。v-modelの`update:xxx`イベント、修飾子（trim/number）の処理、未宣言イベントの警告を含む。

### 本通知の処理概要

この通知は、Vueのコンポーネント間通信の主要な仕組みであり、子コンポーネントが親コンポーネントにカスタムイベントを発行する機能を提供する。emitsオプションによるイベント宣言、バリデーション、v-model統合、修飾子処理、Once修飾子サポート、DevTools連携を包含する。

**業務上の目的・背景**：Vueのコンポーネントアーキテクチャにおいて、子から親への通信はemitを通じて行われる。これはReactのコールバックpropsパターンに相当し、Vueの単方向データフロー（props down, events up）の「events up」部分を担う。emitsオプションによるイベント宣言は型安全性とドキュメンテーションを向上させ、未宣言イベントの警告により開発時のバグ検出を支援する。v-modelディレクティブとの統合により、双方向バインディングの実現基盤としても機能する。

**通知の送信タイミング**：コンポーネント内のsetup関数やメソッドから`emit('eventName', ...args)`が呼び出された時点で即座に発火する。イベント名に対応するハンドラが親コンポーネントのpropsに存在する場合のみ実行される。

**通知の受信者**：親コンポーネント。テンプレートで`v-on:eventName`または`@eventName`で登録したイベントハンドラが呼び出される。DevToolsにもイベント情報が通知される。

**通知内容の概要**：イベント名と引数がハンドラに渡される。v-modelの場合はtrim/number修飾子による引数変換が適用される。Once修飾子付きハンドラは1回のみ実行される。

**期待されるアクション**：親コンポーネントのイベントハンドラが実行され、適切な状態更新やUI変更が行われる。

## 通知種別

コンポーネントイベント（カスタムイベント）/ DevTools連携

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 同期（callWithAsyncErrorHandling経由、Promiseキャッチ付き） |
| 優先度 | 高（ユーザーインタラクションの通知） |
| リトライ | なし |

### 送信先決定ロジック

1. `instance.vnode.props`からイベント名に対応するハンドラを検索
2. 検索順序：(1) `onEventName` → (2) `onCamelizedEventName` → (3) `onHyphenated-event-name`（v-model時のみ）
3. ハンドラが見つかった場合、`callWithAsyncErrorHandling`経由で実行
4. `{handlerName}Once`プロパティが存在する場合、1回限りのハンドラとして追加実行

## 通知テンプレート

### イベント通知の場合

| 項目 | 内容 |
|-----|------|
| 送信元アドレス | N/A（コンポーネント間通信） |
| 送信元名称 | 子コンポーネント |
| 件名 | イベント名（例: `click`, `update:modelValue`） |
| 形式 | JavaScript関数呼び出し |

### 本文テンプレート

```javascript
// 親コンポーネントで登録されたハンドラが呼び出される
handler(...args)  // 通常イベント
handler(trimmedValue)  // v-model + trim修飾子
handler(numericValue)  // v-model + number修飾子
```

### 添付ファイル

| ファイル名 | 形式 | 条件 | 説明 |
|----------|------|------|------|
| N/A | N/A | N/A | コンポーネント間通信のため添付ファイルなし |

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| event | イベント名文字列 | emit()の第1引数 | Yes |
| rawArgs | イベント引数 | emit()の第2引数以降 | No |
| args | 修飾子適用後の引数 | rawArgsにtrim/number修飾子を適用 | No |
| handler | イベントハンドラ関数 | instance.vnode.propsから検索 | No |
| modifiers | v-model修飾子 | propsから取得（trim, number等） | No |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| API呼び出し | emit(event, ...args) | instance.isUnmountedがfalse | コンポーネント内からのemit呼び出し |
| v-model | emit('update:xxx', value) | update:プレフィックス付きイベント | v-modelの双方向バインディング |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| instance.isUnmounted | コンポーネントがアンマウント済みの場合はemitを無視 |
| ハンドラ未登録 | 対応するハンドラがpropsに存在しない場合 |
| Onceハンドラ実行済み | instance.emittedに記録済みの場合はOnceハンドラをスキップ |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A["emit(event, ...rawArgs)"] --> B{isUnmounted?}
    B -->|Yes| Z[return: 何もしない]
    B -->|No| C[propsを取得]
    C --> D{__DEV__モード?}
    D -->|Yes| E[emitsオプションの検証]
    E --> F{イベントが宣言済みか?}
    F -->|No + propsにもない| G["warn: 未宣言イベント警告"]
    F -->|Yes| H{バリデータ関数あり?}
    H -->|Yes| I[バリデータ実行]
    I --> J{バリデーション失敗?}
    J -->|Yes| K["warn: バリデーション失敗警告"]
    D -->|No| L[修飾子チェックへ]
    G --> L
    K --> L
    J -->|No| L
    H -->|No| L
    L --> M{v-model関連イベント?}
    M -->|Yes| N[修飾子取得 trim/number]
    N --> O{trim修飾子?}
    O -->|Yes| P[文字列引数をtrim]
    O -->|No| Q{number修飾子?}
    P --> Q
    Q -->|Yes| R[引数をlooseToNumber変換]
    Q -->|No| S[DevTools通知]
    R --> S
    M -->|No| S
    S --> T{大文字小文字の警告チェック __DEV__}
    T --> U[ハンドラ検索: onEvent → onCamelized → onHyphenated]
    U --> V{ハンドラ発見?}
    V -->|Yes| W[callWithAsyncErrorHandling実行]
    V -->|No| X[スキップ]
    W --> Y{Onceハンドラあり?}
    X --> Y
    Y -->|Yes| AA{実行済み?}
    AA -->|No| AB[emitted記録 + ハンドラ実行]
    AA -->|Yes| AC[スキップ]
    Y -->|No| AD[終了]
```

## データベース参照・更新仕様

### 参照テーブル一覧

| テーブル名 | 用途 | 備考 |
|-----------|------|------|
| N/A | N/A | データベースは使用しない |

### テーブル別参照項目詳細

本通知はデータベースを使用しない。以下のインメモリデータ構造を参照する。

| 参照データ | 用途 | 取得方法 |
|-----------|------|---------|
| instance.vnode.props | イベントハンドラの検索 | VNodeのpropsオブジェクト |
| instance.emitsOptions | emitsオプション（正規化済み） | normalizeEmitsOptionsで生成 |
| instance.propsOptions | propsオプション | normalizePropsOptionsで生成 |
| instance.emitted | Once実行済みイベントの記録 | インスタンスプロパティ |

### 更新テーブル一覧

| テーブル名 | 操作 | 概要 |
|-----------|------|------|
| instance.emitted | UPDATE | Onceハンドラの実行済み記録 |

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| 未宣言イベント警告 | emitsオプションに宣言されていないイベントをemit | __DEV__時にwarn()で警告 |
| バリデーション失敗警告 | emitsオプションのバリデータ関数がfalseを返した | __DEV__時にwarn()で警告 |
| 大文字小文字不一致警告 | イベント名の大文字小文字が一致しない | __DEV__時にwarn()で警告（in-DOMテンプレート向けの注意） |
| ハンドラ実行エラー | ハンドラ内で例外発生 | callWithAsyncErrorHandlingで捕捉し、ErrorCodes.COMPONENT_EVENT_HANDLERとして処理 |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | 0（リトライなし） |
| リトライ間隔 | N/A |
| リトライ対象エラー | N/A |

## 配信設定

### レート制限

| 項目 | 内容 |
|-----|------|
| 1分あたり上限 | なし |
| 1日あたり上限 | なし |

### 配信時間帯

制限なし。

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

- イベント引数はコンポーネント間で直接渡されるため、信頼境界を越える情報の流出リスクは低い
- emitsオプションのバリデータ機能により、不正な引数の検出が可能

## 備考

- `normalizeEmitsOptions`は`WeakMap`によるキャッシュを使用し、emitsオプションの正規化を効率化している
- `isEmitListener`ヘルパーはpropsのキーがemitイベントリスナーかどうかを判定する
- `__COMPAT__`モードではVue 2互換の`$on`/`$off`パターンもサポートされる
- v-modelのイベント名は`update:modelValue`が標準で、`update:propName`でカスタムv-modelをサポート
- ハンドラの検索は3段階：(1) 元のイベント名 → (2) camelCase → (3) kebab-case（v-model時のみ）
- DevToolsへのイベント通知は`__DEV__ || __FEATURE_PROD_DEVTOOLS__`条件で実行される

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | componentEmits.ts | `packages/runtime-core/src/componentEmits.ts` | L36-41: EmitsOptions型（ObjectEmitsOptions | string[]） |
| 1-2 | componentEmits.ts | `packages/runtime-core/src/componentEmits.ts` | L93-109: EmitFn型（emit関数の型定義） |

**読解のコツ**: EmitsOptionsは配列形式（`['click', 'change']`）とオブジェクト形式（`{ click: null, change: (val) => typeof val === 'string' }`）の両方を受け入れる。オブジェクト形式ではバリデータ関数を指定可能。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | componentEmits.ts | `packages/runtime-core/src/componentEmits.ts` | L111-233: emit関数本体。バリデーション、修飾子処理、ハンドラ検索・実行の全フロー |

**主要処理フロー**:
1. **L116**: `instance.isUnmounted`チェック
2. **L119-151**: `__DEV__`モードでのイベント宣言チェックとバリデーション
3. **L153-169**: v-model修飾子（trim/number）の処理
4. **L171-173**: DevTools通知
5. **L175-191**: `__DEV__`での大文字小文字不一致警告
6. **L193-202**: ハンドラ検索（3段階の名前解決）
7. **L204-211**: `callWithAsyncErrorHandling`でハンドラ実行
8. **L213-227**: Onceハンドラの処理（emitted記録 + 実行）

#### Step 3: emitsオプションの正規化を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | componentEmits.ts | `packages/runtime-core/src/componentEmits.ts` | L236-289: normalizeEmitsOptions関数。配列/オブジェクト形式の正規化、mixin/extendsの統合 |

**主要処理フロー**:
- **L241-246**: キャッシュチェック（WeakMap使用）
- **L253-269**: Options API使用時、mixins/extendsのemitsを再帰的に統合
- **L279-283**: 配列形式を`{ key: null }`のオブジェクト形式に変換

#### Step 4: イベントリスナー判定を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | componentEmits.ts | `packages/runtime-core/src/componentEmits.ts` | L294-312: isEmitListener関数。propsキーがemitイベントリスナーかどうかを判定 |

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

```
emit(instance, event, ...rawArgs)         [componentEmits.ts:111]
    |
    +-- [__DEV__] emitsオプション検証        [L119-151]
    |       +-- warn(): 未宣言イベント       [L134-137]
    |       +-- validator(): バリデーション   [L141-142]
    |       +-- warn(): バリデーション失敗    [L144-146]
    |
    +-- v-model修飾子処理                    [L153-169]
    |       +-- getModelModifiers()          [helpers/useModel]
    |       +-- trim / looseToNumber         [@vue/shared]
    |
    +-- devtoolsComponentEmit()             [devtools.ts:162]
    |
    +-- [__DEV__] 大文字小文字警告           [L175-191]
    |       +-- warn()
    |
    +-- ハンドラ検索                          [L193-202]
    |       +-- toHandlerKey(event)
    |       +-- toHandlerKey(camelize(event))
    |       +-- toHandlerKey(hyphenate(event))
    |
    +-- callWithAsyncErrorHandling()         [errorHandling.ts:83]
    |       +-- handler(...args)
    |
    +-- Onceハンドラ処理                      [L213-227]
            +-- instance.emitted記録
            +-- callWithAsyncErrorHandling()
```

### データフロー図

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

event (string)            +-- emit()                        +-- handler(...args)
  |                       |     |                           |     (親のイベントハンドラ)
rawArgs (...any[])   ---->|     +-- バリデーション           |
  |                       |     +-- 修飾子処理          ---->+-- DevTools通知
instance              --->|     +-- ハンドラ検索             |     (component:emit)
  |                       |     +-- ハンドラ実行             |
  |                       |     +-- Onceハンドラ処理         +-- instance.emitted更新
props (VNode) --------->  +--                                     (Once記録)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| componentEmits.ts | `packages/runtime-core/src/componentEmits.ts` | ソース | emit関数本体、normalizeEmitsOptions、isEmitListener |
| errorHandling.ts | `packages/runtime-core/src/errorHandling.ts` | ソース | callWithAsyncErrorHandling（ハンドラ実行） |
| devtools.ts | `packages/runtime-core/src/devtools.ts` | ソース | devtoolsComponentEmit（DevTools通知） |
| warning.ts | `packages/runtime-core/src/warning.ts` | ソース | warn関数（開発モード警告） |
| useModel.ts | `packages/runtime-core/src/helpers/useModel.ts` | ソース | getModelModifiers（v-model修飾子取得） |
| shared | `@vue/shared` | ソース | camelize, hyphenate, toHandlerKey, looseToNumber等のユーティリティ |
