# 機能設計書 20-onBeforeUnmount() / onUnmounted()

## 概要

本ドキュメントは、コンポーネント破棄前後に実行されるクリーンアップ用ライフサイクルフック `onBeforeUnmount()` と `onUnmounted()` の機能設計書である。

### 本機能の処理概要

`onBeforeUnmount()` はコンポーネントがDOMから除去される直前に実行されるフックであり、`onUnmounted()` はコンポーネントの完全な破棄後に実行されるフックである。リソースのクリーンアップ（イベントリスナーの解除、タイマーの解除、WebSocket接続の切断等）に使用される。

**業務上の目的・背景**：コンポーネントのライフサイクル終了時に確実にリソースを解放し、メモリリークや不要な副作用の残存を防止する。特に `onMounted` で登録したイベントリスナーや外部リソースの後始末に不可欠である。

**機能の利用シーン**：
- `onBeforeUnmount()`: DOMがまだ利用可能な状態でのクリーンアップ（スクロール位置の保存、DOM測定値の記録等）
- `onUnmounted()`: イベントリスナーの解除、setInterval/setTimeoutのクリア、WebSocket切断、外部ライブラリのdestroy、購読の解除

**主要な処理内容**：
1. `createHook(LifecycleHooks.BEFORE_UNMOUNT)` / `createHook(LifecycleHooks.UNMOUNTED)` でフック登録関数を生成
2. `injectHook()` でコンポーネントインスタンスのフック配列（`bum` / `um`）にコールバックを追加
3. `onBeforeUnmount` のフックは `unmountComponent` 関数内で `invokeArrayFns(bum)` として同期実行される
4. `onUnmounted` のフックは `unmountComponent` 関数内で `queuePostRenderEffect(um)` としてキュー実行される
5. unmountComponentでは `scope.stop()` によるエフェクトスコープの停止、ジョブの破棄も行われる

**関連システム・外部連携**：レンダラー（`renderer.ts`）の `unmountComponent` 関数で呼び出される。

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

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | 該当なし | - | onBeforeUnmount/onUnmountedは汎用フックであり、画面機能マッピングに直接対応する画面はない |

## 機能種別

ライフサイクルフック / クリーンアップ処理

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| hook | () => any | Yes | 破棄前/後に実行するコールバック関数 | - |
| target | ComponentInternalInstance \| null | No | フック登録先のコンポーネントインスタンス。デフォルトはcurrentInstance | アクティブなインスタンスがない場合はDEVで警告 |

### 入力データソース

`setup()` 関数内または Composition API 関数内で開発者が直接呼び出す。

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| 戻り値 | void | 戻り値なし |

### 出力先

コンポーネントインスタンスの内部フック配列（`instance.bum` / `instance.um`）に登録される。

## 処理フロー

### 処理シーケンス

```
1. createHook(LifecycleHooks.BEFORE_UNMOUNT / UNMOUNTED) で登録関数を生成
2. 登録関数呼び出し時:
   ├─ SSRコンポーネントセットアップ中の場合: 登録をスキップ
   └─ 通常: injectHook() を呼び出し
3. unmountComponent(instance, parentSuspense, doRemove) での実行:
   ├─ invalidateMount(m): マウントフックのキュー済みエフェクトを無効化
   ├─ invalidateMount(a): activatedフックのキュー済みエフェクトを無効化
   ├─ invokeArrayFns(bum): onBeforeUnmountフックを同期実行
   ├─ scope.stop(): エフェクトスコープの停止
   ├─ job.flags |= DISPOSED: レンダリングジョブの破棄
   ├─ unmount(subTree, instance, parentSuspense, doRemove): サブツリーのアンマウント
   ├─ queuePostRenderEffect(um): onUnmountedフックをキュー実行
   └─ instance.isUnmounted = true: アンマウント完了フラグ
```

### フローチャート

```mermaid
flowchart TD
    A[コンポーネントアンマウント要求] --> B[unmountComponent呼び出し]
    B --> C[invalidateMount m, a]
    C --> D{bum フックあり?}
    D -->|Yes| E[invokeArrayFns bum 同期実行]
    D -->|No| F[スキップ]
    E --> G[scope.stop]
    F --> G
    G --> H{job存在?}
    H -->|Yes| I[job.flags DISPOSED設定]
    I --> J[unmount subTree]
    H -->|No| K[スキップ]
    K --> J
    J --> L{um フックあり?}
    L -->|Yes| M[queuePostRenderEffect um]
    L -->|No| N[スキップ]
    M --> O[instance.isUnmounted = true]
    N --> O
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-20-01 | SSR時のスキップ | SSRコンポーネントセットアップ中はフック登録がスキップされる | isInSSRComponentSetupがtrueの場合 |
| BR-20-02 | onBeforeUnmountの同期実行 | onBeforeUnmountフックはサブツリーアンマウント前に同期的に実行される | unmountComponent内 |
| BR-20-03 | onUnmountedの非同期キュー | onUnmountedフックはサブツリーアンマウント後にqueuePostRenderEffectでキュー実行される | unmountComponent内 |
| BR-20-04 | エフェクトスコープ停止 | onBeforeUnmount後にscope.stop()でコンポーネント内の全エフェクトが停止される | unmountComponent内 |
| BR-20-05 | ジョブ破棄 | レンダリングジョブにDISPOSEDフラグが設定され、スケジューラが以降ジョブを実行しなくなる | unmountComponent内 |
| BR-20-06 | マウントフック無効化 | アンマウント時にキュー済みのmounted/activatedフックが無効化される | invalidateMount(m), invalidateMount(a) |
| BR-20-07 | DOM可用性 | onBeforeUnmount時にはDOMがまだ利用可能、onUnmounted時にはDOMが除去済み | 各フックの実行タイミングにより |

### 計算ロジック

特に複雑な計算ロジックは存在しない。

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

該当なし。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | 警告（DEV） | アクティブなコンポーネントインスタンスがない状態での呼び出し | setup()内で呼び出す |
| LifecycleHooks.BEFORE_UNMOUNT / UNMOUNTED | ランタイムエラー | フック内で発生した例外 | callWithAsyncErrorHandlingで捕捉 |

### リトライ仕様

該当なし。

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

該当なし。

## パフォーマンス要件

- クリーンアップフックは迅速に完了すべきである。重い非同期処理はonBeforeUnmount内で開始し、完了を待たずに進行させることが推奨される。

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

- 外部リソース（WebSocket、EventSource等）への接続を確実にクローズし、データ漏洩を防止すること。

## 備考

- `onBeforeUnmount` のフックはインスタンスの `bum` プロパティに格納される（LifecycleHooks.BEFORE_UNMOUNT = 'bum'）。
- `onUnmounted` のフックはインスタンスの `um` プロパティに格納される（LifecycleHooks.UNMOUNTED = 'um'）。
- Options APIの `beforeUnmount` / `unmounted` と同等の機能をComposition APIで提供する。
- `scope.stop()` はコンポーネント内で作成された全てのリアクティブエフェクト（watch、computed等）を停止する。
- `instance.isUnmounted = true` 設定後は、非同期処理のコールバック内でこのフラグをチェックして処理を中断できる。

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | enums.ts | `packages/runtime-core/src/enums.ts` | LifecycleHooks列挙型（BEFORE_UNMOUNT='bum', UNMOUNTED='um'） |

#### Step 2: フック登録メカニズムを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | apiLifecycle.ts | `packages/runtime-core/src/apiLifecycle.ts` | onBeforeUnmount/onUnmountedの定義（91-94行目） |
| 2-2 | apiLifecycle.ts | `packages/runtime-core/src/apiLifecycle.ts` | createHookファクトリ（66-79行目）、injectHook（20-64行目） |

**主要処理フロー**:
- **91-92行目**: `onBeforeUnmount = createHook(LifecycleHooks.BEFORE_UNMOUNT)`
- **94行目**: `onUnmounted = createHook(LifecycleHooks.UNMOUNTED)`

#### Step 3: レンダラーでの実行を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | renderer.ts | `packages/runtime-core/src/renderer.ts` | unmountComponent関数（2307-2370行目） |

**主要処理フロー**:
- **2316行目**: `const { bum, scope, job, subTree, um, m, a } = instance` で変数を分解
- **2317-2318行目**: `invalidateMount(m)`, `invalidateMount(a)` でキュー済みマウント/アクティベーションフックを無効化
- **2321-2323行目**: `if (bum) { invokeArrayFns(bum) }` でbeforeUnmountフックを同期実行
- **2333行目**: `scope.stop()` でエフェクトスコープを停止
- **2339行目**: `job.flags! |= SchedulerJobFlags.DISPOSED` でジョブを破棄
- **2340行目**: `unmount(subTree, instance, parentSuspense, doRemove)` でサブツリーアンマウント
- **2343-2344行目**: `if (um) { queuePostRenderEffect(um, parentSuspense) }` でunmountedフックをキュー
- **2358行目**（付近）: `instance.isUnmounted = true` でアンマウント完了フラグ設定

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

```
onBeforeUnmount(hook) / onUnmounted(hook)
    │
    └─ createHook(lifecycle)
           └─ injectHook(lifecycle, hook, target)

レンダラー unmountComponent(instance, parentSuspense, doRemove):
    │
    ├─ invalidateMount(m)              ← mountedフック無効化
    ├─ invalidateMount(a)              ← activatedフック無効化
    ├─ invokeArrayFns(bum)             ← onBeforeUnmountフック実行
    ├─ scope.stop()                    ← エフェクトスコープ停止
    ├─ job.flags |= DISPOSED           ← レンダリングジョブ破棄
    ├─ unmount(subTree)                ← サブツリーのアンマウント
    │      └─ 再帰的に子コンポーネントもアンマウント
    ├─ queuePostRenderEffect(um)       ← onUnmountedフックをキュー
    └─ instance.isUnmounted = true     ← アンマウント完了
```

### データフロー図

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

hookコールバック ──────▶ injectHook()              ──▶ instance.bum[] / instance.um[]
                              │
コンポーネント破棄   ──────▶ unmountComponent()
                              │
                       ├─ invalidateMount(m, a)  ──▶ キュー済みフック無効化
                       ├─ invokeArrayFns(bum)    ──▶ フック同期実行 (beforeUnmount)
                       ├─ scope.stop()           ──▶ 全エフェクト停止
                       ├─ unmount(subTree)        ──▶ DOM除去
                       └─ queuePostRenderEffect(um) ▶ フック非同期実行 (unmounted)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| apiLifecycle.ts | `packages/runtime-core/src/apiLifecycle.ts` | ソース | onBeforeUnmount/onUnmountedの定義 |
| enums.ts | `packages/runtime-core/src/enums.ts` | ソース | LifecycleHooks列挙型 |
| renderer.ts | `packages/runtime-core/src/renderer.ts` | ソース | unmountComponent関数でのフック呼び出し |
| component.ts | `packages/runtime-core/src/component.ts` | ソース | ComponentInternalInstance |
| errorHandling.ts | `packages/runtime-core/src/errorHandling.ts` | ソース | callWithAsyncErrorHandling |
| scheduler.ts | `packages/runtime-core/src/scheduler.ts` | ソース | queuePostRenderEffect, SchedulerJobFlags.DISPOSED |
