# 機能設計書 5-watch() / watchEffect()

## 概要

本ドキュメントは、Vue 3のリアクティビティシステムにおける`watch()`および`watchEffect()` APIの機能設計を記述する。これらのAPIはリアクティブデータの変更を監視し、変更時に副作用を実行する。

### 本機能の処理概要

`watch()`は特定のリアクティブソース（ref, reactive, getter関数）を監視し、変更時にコールバックを実行するAPIである。`watchEffect()`は関数内でアクセスしたリアクティブデータを自動追跡し、変更時に再実行するAPIである。両者はReactiveEffectを内部で使用し、`@vue/reactivity`パッケージの基本watch実装をruntime-coreでラップしてフラッシュタイミング（pre/post/sync）を制御する。

**業務上の目的・背景**：リアクティブデータの変更に応じた副作用（APIコール、DOM操作、ストレージ保存など）を宣言的に定義するために不可欠なAPIである。computed()が値の導出に特化するのに対し、watch/watchEffectは副作用の実行に特化している。

**機能の利用シーン**：URLハッシュの永続化（watchEffect）、Vueバージョン変更時のページリロード制御（watch）、非同期データフェッチ、フォーム入力のデバウンス処理、WebSocket接続管理など。

**主要な処理内容**：
1. ソース種別の判定（ref, reactive, array, function）と対応するgetterの構築
2. deep/onceオプションの処理
3. ReactiveEffectの生成とスケジューラの設定
4. フラッシュタイミング（pre/post/sync）の制御
5. クリーンアップ関数の管理
6. WatchHandleの返却（stop/pause/resume機能付き）

**関連システム・外部連携**：基本ロジックは`@vue/reactivity`パッケージの`watch()`で実装。runtime-coreの`apiWatch.ts`でflush制御とコンポーネントライフサイクルとの統合を行う。

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

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 1 | App（メイン画面） | 主画面 | watchEffectでURLハッシュ永続化、watchでVueバージョン変更時のページリロード制御 |

## 機能種別

リアクティビティ管理 / 副作用実行API

## 入力仕様

### 入力パラメータ

#### watch()

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| source | WatchSource \| WatchSource[] \| object | Yes | 監視対象。ref, reactive, getter関数、またはこれらの配列 | ref/reactive/function/array型 |
| cb | WatchCallback | Yes | 変更時に実行するコールバック | 関数であること |
| options | WatchOptions | No | immediate, deep, once, flush等のオプション | - |

#### watchEffect()

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| effect | WatchEffect | Yes | 実行する副作用関数 | 関数であること |
| options | WatchEffectOptions | No | flush等のオプション | - |

### 入力データソース

APIの引数として直接渡される。

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| WatchHandle | WatchHandle | stop(), pause(), resume()メソッドを持つハンドル |

### 出力先

呼び出し元に返却。stop()呼び出しで監視を停止。

## 処理フロー

### 処理シーケンス

```
1. watch(source, cb, options) / watchEffect(effect, options)が呼び出される
2. doWatch(source, cb/null, options)に委譲（runtime-core）
3. flushオプションに基づいてschedulerを設定
   ├─ 'post': queuePostRenderEffect
   ├─ 'sync': 直接実行
   └─ 'pre'(デフォルト): queueJob
4. baseWatch(source, cb, baseWatchOptions)を呼び出す（@vue/reactivity）
5. ソース種別に応じたgetterを構築
   ├─ isRef: () => source.value
   ├─ isReactive: () => reactiveGetter(source)
   ├─ isArray: source.map(...)
   ├─ isFunction + cb: source自体をgetter
   └─ isFunction without cb: watchEffect用getter
6. deep && cb の場合、traverse()でネストプロパティを走査
7. new ReactiveEffect(getter)を生成
8. 初回実行
   ├─ cb + immediate: job(true)で即時実行
   ├─ cb + !immediate: effect.run()で初期値取得
   └─ !cb: effect.run()で即時実行（watchEffect）
9. WatchHandle(stop/pause/resume)を返却
```

### フローチャート

```mermaid
flowchart TD
    A[watch/watchEffect呼び出し] --> B[doWatch source, cb, options]
    B --> C[flushに基づくscheduler設定]
    C --> D[baseWatch source, cb, baseWatchOptions]
    D --> E{ソース種別判定}
    E -->|isRef| F[getter = source.value]
    E -->|isReactive| G[getter = reactiveGetter source]
    E -->|isArray| H[getter = source.map ...]
    E -->|isFunction + cb| I[getter = source]
    E -->|isFunction no cb| J[getter = watchEffect用]
    F --> K{deep && cb?}
    G --> K
    H --> K
    I --> K
    J --> L[new ReactiveEffect getter]
    K -->|Yes| M[getter = traverse baseGetter, depth]
    K -->|No| L
    M --> L
    L --> N{初回実行}
    N -->|immediate| O[job true]
    N -->|!immediate + cb| P[effect.run で初期値取得]
    N -->|watchEffect| Q[effect.run 即時実行]
    O --> R[WatchHandle返却]
    P --> R
    Q --> R
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-01 | ソース多重対応 | ref, reactive, getter関数、配列を監視ソースとして受け付ける | watch()のソース引数 |
| BR-02 | flush制御 | pre(デフォルト)/post/syncでコールバック実行タイミングを制御 | flushオプション |
| BR-03 | deep監視 | deep: true/numberでネストされたプロパティの変更も検知 | deepオプション |
| BR-04 | once実行 | once: trueで初回変更検知後に自動停止 | onceオプション |
| BR-05 | immediate実行 | immediate: trueで初回即時実行（初期値も含む） | immediateオプション |
| BR-06 | クリーンアップ | onCleanup/onWatcherCleanupで前回の副作用をクリーンアップ | コールバック再実行時 |
| BR-07 | SSR対応 | SSRセットアップ中はsync以外のwatchは即停止ハンドルを返す | isInSSRComponentSetup時 |

### 計算ロジック

- `traverse(value, depth)`でネストされたリアクティブデータを再帰的に走査し、全ての依存を収集する
- `hasChanged(newValue, oldValue)`で値変更を検知し、変更がある場合のみコールバックを実行する

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

該当なし

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| WatchErrorCodes.WATCH_GETTER (2) | ランタイムエラー | getterの実行でエラー発生 | callWithAsyncErrorHandling経由でerrorHandlerに通知 |
| WatchErrorCodes.WATCH_CALLBACK (3) | ランタイムエラー | コールバック実行でエラー発生 | callWithAsyncErrorHandling経由でerrorHandlerに通知 |
| WatchErrorCodes.WATCH_CLEANUP (4) | ランタイムエラー | クリーンアップ関数の実行でエラー | callWithAsyncErrorHandling経由でerrorHandlerに通知 |
| - | 開発者警告 | watchEffect()にimmediate/deep/onceオプションを渡した場合 | コンソール警告 |
| - | 開発者警告 | 無効なソースを渡した場合 | コンソール警告 |

### リトライ仕様

該当なし

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

該当なし

## パフォーマンス要件

- ReactiveEffectのdirtyチェックで不要なコールバック実行を防止
- flushタイミングの制御で同一ティック内の複数変更をバッチ処理
- deep: false/0でルートレベルのみ監視（traverse深度1）

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

特になし

## 備考

- `watchPostEffect()`は`flush: 'post'`の省略形
- `watchSyncEffect()`は`flush: 'sync'`の省略形
- `instanceWatch()`はOptions APIの`this.$watch()`を実装する
- `createPathGetter()`はドット区切りパス文字列の監視に対応する

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | watch.ts | `packages/reactivity/src/watch.ts` | WatchOptions, WatchSource, WatchCallback, WatchHandle型定義（37-75行目） |
| 1-2 | apiWatch.ts | `packages/runtime-core/src/apiWatch.ts` | WatchEffectOptions, WatchOptions（runtime-core版）（45-53行目） |

**読解のコツ**: watchの実装は2層構造。基本ロジックは`@vue/reactivity`の`watch.ts`にあり、runtime-coreの`apiWatch.ts`がflush制御やエラーハンドリングをラップしている。この層分けを意識して読むこと。

#### Step 2: エントリーポイントを理解する（runtime-core層）

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | apiWatch.ts | `packages/runtime-core/src/apiWatch.ts` | watchEffect()（56-61行目）、watch()（131-144行目） |
| 2-2 | apiWatch.ts | `packages/runtime-core/src/apiWatch.ts` | doWatch()（146-242行目）。flush制御とbaseWatch呼び出し |

**主要処理フロー**:
1. **56-61行目**: watchEffect()。doWatch(effect, null, options)を呼び出す
2. **131-144行目**: watch()。cbが関数でない場合に警告。doWatch(source, cb, options)を呼び出す
3. **200-214行目**: flushに基づくscheduler設定。'post'ならqueuePostRenderEffect、'pre'ならqueueJob
4. **231行目**: baseWatch(source, cb, baseWatchOptions)を呼び出す

#### Step 3: 基本watchロジックを理解する（@vue/reactivity層）

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | watch.ts | `packages/reactivity/src/watch.ts` | watch()関数（120-329行目）。ソース種別判定、getter構築、ReactiveEffect生成 |

**主要処理フロー**:
- **153-205行目**: ソース種別に応じたgetter構築（isRef/isReactive/isArray/isFunction）
- **207-211行目**: deep && cbの場合、traverse()でgetter結果を走査
- **221-227行目**: once: trueの場合、初回コールバック後にwatchHandleで停止
- **286-291行目**: ReactiveEffect生成とscheduler設定
- **312-322行目**: 初回実行ロジック（immediate/scheduler/effect.run）

#### Step 4: traverse()を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | watch.ts | `packages/reactivity/src/watch.ts` | traverse()（331-367行目）。再帰的なプロパティ走査 |

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

```
watchEffect(effect, options) / watch(source, cb, options)
    |
    +-- doWatch(source, cb/null, options)     [runtime-core/apiWatch.ts]
            |
            +-- flush制御: scheduler設定
            |       +-- 'post': queuePostRenderEffect
            |       +-- 'pre': queueJob
            |       +-- 'sync': 直接実行
            |
            +-- baseWatch(source, cb, baseWatchOptions)  [@vue/reactivity/watch.ts]
                    |
                    +-- ソース種別判定 & getter構築
                    |       +-- isRef: () => source.value
                    |       +-- isReactive: () => reactiveGetter(source)
                    |       +-- isArray: source.map(...)
                    |       +-- isFunction: source / watchEffect用ラッパー
                    |
                    +-- deep: traverse(getter(), depth)
                    |
                    +-- new ReactiveEffect(getter)
                    |       +-- effect.scheduler = scheduler
                    |
                    +-- 初回実行
                    |       +-- immediate: job(true)
                    |       +-- !immediate: effect.run()
                    |
                    +-- WatchHandle返却
                            +-- stop: effect.stop()
                            +-- pause: effect.pause()
                            +-- resume: effect.resume()
```

### データフロー図

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

source (ref/reactive/fn) ──> doWatch()                    ──> WatchHandle
cb (callback)                   |
options (flush/deep/...)        |
                          baseWatch()
                                |
                          getter構築
                                |
                          ReactiveEffect生成
                                |
                          +-----+-----+
                          |           |
                     初回実行    変更検知時
                          |           |
                     oldValue    newValue計算
                     取得        hasChanged比較
                                      |
                                 cb(newValue, oldValue, onCleanup)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| watch.ts | `packages/reactivity/src/watch.ts` | ソース | 基本watch実装。ソース判定、getter構築、traverse() |
| apiWatch.ts | `packages/runtime-core/src/apiWatch.ts` | ソース | runtime-coreラッパー。flush制御、エラーハンドリング、SSR対応 |
| effect.ts | `packages/reactivity/src/effect.ts` | ソース | ReactiveEffect。watchの内部エフェクト |
| scheduler.ts | `packages/runtime-core/src/scheduler.ts` | ソース | queueJob()。pre/post flush制御 |
| errorHandling.ts | `packages/runtime-core/src/errorHandling.ts` | ソース | callWithAsyncErrorHandling。エラーハンドリング |
