# 機能設計書 37-KeepAlive

## 概要

本ドキュメントは、Vue 3の組み込みコンポーネント`<KeepAlive>`の機能設計を記載する。`<KeepAlive>`はコンポーネントインスタンスをキャッシュし、非アクティブ時にDOMからは除去するがインスタンスを保持して再利用することで、状態を維持したまま高速な切替を実現するコンポーネントである。

### 本機能の処理概要

`<KeepAlive>`コンポーネントは、動的コンポーネントやルーターにおけるコンポーネント切替時に、コンポーネントインスタンスをメモリ上にキャッシュする。キャッシュされたコンポーネントは再表示時にマウント処理を省略し、高速に切り替えられる。`include`/`exclude`/`max`プロパティによりキャッシュ対象を制御でき、LRU(Least Recently Used)方式でキャッシュサイズを管理する。

**業務上の目的・背景**：タブ切替やルート遷移の際にコンポーネントの状態（フォーム入力、スクロール位置等）を保持したい場面は多い。毎回破棄・再生成すると状態が失われるだけでなく、パフォーマンスコストも発生する。KeepAliveはこれらの問題を解決する。

**機能の利用シーン**：タブベースのUI、ルーターでのページ切替、ウィザード形式のフォーム、ダッシュボードのパネル切替等。

**主要な処理内容**：
1. `Map<CacheKey, VNode>`によるキャッシュ管理
2. `Set<CacheKey>`によるLRUキー順序管理
3. `activate`/`deactivate`メソッドのRendererへの提供
4. `include`/`exclude`のwatchによる動的キャッシュ制御
5. `max`プロパティによるLRUキャッシュ削除
6. `ShapeFlags`のビット操作によるKeepAlive状態管理
7. `onActivated`/`onDeactivated`ライフサイクルフックのサポート
8. Suspenseとの連携

**関連システム・外部連携**：レンダラー（`renderer.ts`）のprocess/moveメソッドと連携。`BaseTransition`のトランジションフックと連携。`Suspense`コンポーネントとの連携。

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

## 関連画面

画面機能マッピングに該当する画面関連情報なし。

## 機能種別

状態管理 / キャッシュ制御 / 組み込みコンポーネント

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| include | `string \| RegExp \| (string \| RegExp)[]` | No | キャッシュ対象のコンポーネント名パターン | - |
| exclude | `string \| RegExp \| (string \| RegExp)[]` | No | キャッシュ除外のコンポーネント名パターン | - |
| max | `number \| string` | No | キャッシュの最大数 | 数値に変換される |

### 入力データソース

- デフォルトスロットの子コンポーネント（1つのみ許可）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| VNode | `VNode` | キャッシュ処理済みの子VNode |
| activate() | `Function` | キャッシュされたコンポーネントの再アクティベーション処理 |
| deactivate() | `Function` | コンポーネントのデアクティベーション処理（DOMから除去しメモリに保持） |

### 出力先

- レンダラーの内部コンテキスト（`KeepAliveContext`経由）
- onActivated/onDeactivatedライフサイクルフック呼び出し

## 処理フロー

### 処理シーケンス

```
初回レンダリング:
1. スロットから子コンポーネント取得
2. include/excludeによるフィルタリング
3. pendingCacheKey設定
4. COMPONENT_SHOULD_KEEP_ALIVE フラグ設定
5. onMountedでcacheにsubTreeを保存

切替時（activate）:
1. move()でstorageContainerからメインコンテナに移動
2. patch()でprops更新
3. queuePostRenderEffect()でonActivatedフック呼び出し

切替時（deactivate）:
1. move()でメインコンテナからstorageContainerに移動
2. queuePostRenderEffect()でonDeactivatedフック呼び出し
3. isDeactivated = true

キャッシュヒット時:
1. cachedVNodeからel/componentをコピー
2. COMPONENT_KEPT_ALIVE フラグ設定
3. keysの順序を更新（LRU）
```

### フローチャート

```mermaid
flowchart TD
    A[render関数呼び出し] --> B{子要素あり?}
    B -->|No| C[null返却]
    B -->|Yes| D{1つのステートフルコンポーネント?}
    D -->|No| E[そのまま返却]
    D -->|Yes| F{include/excludeフィルタ通過?}
    F -->|No| G[フラグなしで返却]
    F -->|Yes| H{キャッシュにkey存在?}
    H -->|Yes| I[el/componentをコピー, KEPT_ALIVEフラグ設定, LRU更新]
    H -->|No| J[keysにkey追加]
    J --> K{max超過?}
    K -->|Yes| L[最古のエントリを削除]
    K -->|No| M[SHOULD_KEEP_ALIVEフラグ設定]
    I --> M
    L --> M
    M --> N[VNode返却]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-37-01 | 単一子要素 | KeepAliveは正確に1つのコンポーネント子要素のみ許可 | 常時（複数子は警告） |
| BR-37-02 | LRUキャッシュ | maxを超過した場合、最も古い（最初に追加された）エントリを削除 | max指定時 |
| BR-37-03 | include/excludeマッチング | コンマ区切り文字列、正規表現、配列でのマッチングをサポート | include/exclude指定時 |
| BR-37-04 | 非同期コンポーネント対応 | 非同期コンポーネントの場合、名前チェックは内部のresolved componentに基づく | isAsyncWrapper時 |
| BR-37-05 | Suspense連携 | 子がSuspenseの場合、resolveを待ってからキャッシュ | isSuspense時 |
| BR-37-06 | SSRバイパス | SSR時はレンダラーが未登録のため、KeepAliveは無視されスロットのみを返却 | `__SSR__`かつrenderer未登録時 |
| BR-37-07 | include/exclude動的監視 | include/excludeの変更をwatchで監視し、マッチしなくなったキャッシュを削除 | include/exclude変更時 |

### 計算ロジック

`matches`関数: 文字列はカンマ区切りで`includes`チェック。正規表現は`test`チェック（`lastIndex`をリセット）。配列は再帰的にマッチング。

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

該当なし。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | 開発時警告 | 複数の子コンポーネントが渡された | `KeepAlive should contain exactly one component child.` |

### リトライ仕様

該当なし。

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

該当なし。

## パフォーマンス要件

- `storageContainer`（非表示div要素）を使用して、deactivateされたコンポーネントをオフスクリーンに保持
- LRUキャッシュ戦略により、メモリ使用量を制御
- `ShapeFlags`のビット操作による高速な状態判定

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

- キャッシュされたコンポーネントはメモリに保持されるため、機密データを含むコンポーネントのキャッシュには注意が必要

## 備考

- `__isKeepAlive`マーカーにより、レンダラーはKeepAliveをインポートせずに検出可能（tree-shaking対応）
- `resetShapeFlag`でKEEP_ALIVEフラグをビット演算でリセット
- `getInnerChild`でSuspenseの内部コンテンツ（ssContent）を取得
- `onActivated`/`onDeactivated`は子孫のKeepAliveルートにも伝播される

---

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

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

### 推奨読解順序

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

KeepAliveのProps、キャッシュ構造、コンテキストを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | KeepAlive.ts | `packages/runtime-core/src/components/KeepAlive.ts` | `KeepAliveProps`（54-58行目）、`Cache`/`Keys`型（60-62行目）、`KeepAliveContext`（64-74行目） |

#### Step 2: setup関数を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | KeepAlive.ts | `packages/runtime-core/src/components/KeepAlive.ts` | `setup()`（93-373行目） |

**主要処理フロー**:
1. **111-113行目**: cache/keys/currentの初期化
2. **131-167行目**: `activate`メソッド - move + patch + ライフサイクル呼び出し
3. **169-195行目**: `deactivate`メソッド - move to storageContainer + ライフサイクル呼び出し
4. **203-216行目**: `pruneCache`関数 - フィルタによるキャッシュ削除
5. **232-240行目**: include/excludeのwatch
6. **277-373行目**: レンダリング関数 - キャッシュヒット/ミスの処理

#### Step 3: ライフサイクルフック登録を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | KeepAlive.ts | `packages/runtime-core/src/components/KeepAlive.ts` | `onActivated`/`onDeactivated`関数（408-420行目）、`registerKeepAliveHook`（422-458行目） |

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

```
<KeepAlive>
    │
    ├─ setup()
    │      ├─ cache = new Map()  /  keys = new Set()
    │      ├─ sharedContext.activate = (vnode, container, ...)
    │      │      ├─ move(vnode, container, anchor, ENTER)
    │      │      ├─ patch(instance.vnode, vnode, ...)
    │      │      └─ invokeArrayFns(instance.a) [onActivatedフック]
    │      ├─ sharedContext.deactivate = (vnode)
    │      │      ├─ move(vnode, storageContainer, null, LEAVE)
    │      │      └─ invokeArrayFns(instance.da) [onDeactivatedフック]
    │      ├─ watch([include, exclude]) → pruneCache()
    │      ├─ onMounted(cacheSubtree)
    │      ├─ onUpdated(cacheSubtree)
    │      ├─ onBeforeUnmount → cache.forEach(unmount)
    │      └─ return () => { ... } (レンダリング関数)
    │             ├─ matches(include/exclude, name)
    │             ├─ cache.get(key) → キャッシュヒット処理
    │             └─ keys LRU管理
    │
    ├─ onActivated(hook) → registerKeepAliveHook(ACTIVATED)
    └─ onDeactivated(hook) → registerKeepAliveHook(DEACTIVATED)
```

### データフロー図

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

子コンポーネント      ┌──────────────────────────┐
  (defaultスロット) ──▶│ レンダリング関数            │
                    │  ├ include/excludeフィルタ  │
                    │  ├ キャッシュ検索            │
                    │  │  ├ ヒット→el/component復元│
                    │  │  └ ミス→keys追加,max制御  │
                    │  └ フラグ設定               │──────▶ VNode (KEEP_ALIVE flags)
                    └──────────────────────────┘

切替時               ┌──────────────────────────┐
  deactivate ────────▶│ move to storageContainer  │──────▶ インスタンス保持
  activate ──────────▶│ move to mainContainer    │──────▶ DOM復元 + patch
                    └──────────────────────────┘
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| KeepAlive.ts | `packages/runtime-core/src/components/KeepAlive.ts` | ソース | KeepAliveコンポーネントのメイン実装 |
| renderer.ts | `packages/runtime-core/src/renderer.ts` | ソース | activate/deactivateの呼び出し元 |
| BaseTransition.ts | `packages/runtime-core/src/components/BaseTransition.ts` | ソース | setTransitionHooksの提供 |
| Suspense.ts | `packages/runtime-core/src/components/Suspense.ts` | ソース | Suspenseとの連携 |
| apiLifecycle.ts | `packages/runtime-core/src/apiLifecycle.ts` | ソース | ライフサイクルフックの注入 |
| index.ts | `packages/runtime-core/src/index.ts` | ソース | KeepAliveのエクスポート |
