# 機能設計書 3-readonly()

## 概要

本ドキュメントは、Vue 3のリアクティビティシステムにおける`readonly()` APIの機能設計を記述する。`readonly()`はリアクティブまたはプレーンなオブジェクトを読み取り専用のリアクティブプロキシに変換し、書き込み操作を禁止するAPIである。

### 本機能の処理概要

`readonly()`は、対象オブジェクト（reactive/plainオブジェクトまたはRef）に対して読み取り専用のリアクティブプロキシを生成する。読み取り操作では依存性追跡が行われるが、書き込み・削除操作は開発モードで警告を出して拒否される。ネストされたプロパティも再帰的に読み取り専用となる（深いreadonly）。

**業務上の目的・背景**：状態の不変性を保証するために使用される。Propsやストアの状態を子コンポーネントに渡す際、意図しない変更を防止する目的で利用される。reactiveオブジェクトのreadonlyラッパーを作成すると、元のreactiveオブジェクトの変更はreadonly経由で読み取れるが、readonly経由での変更は不可能となる。

**機能の利用シーン**：Propsの読み取り専用保護、ストア状態の外部公開、computed refの内部実装（setter未定義時）、定数オブジェクトの保護などに使用される。

**主要な処理内容**：
1. createReactiveObject()でreadonly用Proxyを生成（readonlyHandlersを使用）
2. getハンドラでtrack()による依存性追跡（readonlyでもリアクティブ性は維持）
3. setハンドラで書き込み操作を拒否（開発モードで警告）
4. deletePropertyハンドラで削除操作を拒否（開発モードで警告）
5. ネストされたオブジェクトアクセス時に遅延的にreadonly()で変換
6. shallowReadonly()でルートレベルのみ読み取り専用にする機能の提供

**関連システム・外部連携**：`@vue/reactivity`パッケージに所属。reactive()と同じcreateReactiveObject()を使用するが、readonlyHandlersとreadonlyMapを渡すことで読み取り専用動作を実現する。

**権限による制御**：readonlyプロキシ経由の書き込みは常に拒否される。ただし、元のreactiveオブジェクトを直接操作することは可能であり、その変更はreadonly経由で反映される。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | - | - | 画面機能マッピングにreadonly()単体の関連画面は登録されていない |

## 機能種別

リアクティビティ管理 / データ保護API

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| target | T extends object | Yes | 読み取り専用プロキシに変換する対象オブジェクト | オブジェクト型であること |

### 入力データソース

APIの引数として直接渡されるオブジェクト（reactive/plainオブジェクトまたはRef）。

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| DeepReadonly\<UnwrapNestedRefs\<T\>\> | 読み取り専用プロキシ | 深い読み取り専用プロキシ。書き込み操作は拒否される |

### 出力先

呼び出し元のComposition APIコンテキストに返却。

## 処理フロー

### 処理シーケンス

```
1. readonly(target)が呼び出される
   └─ 対象オブジェクトを受け取る
2. createReactiveObject()を呼び出す
   ├─ isReadonly=true, readonlyHandlers, readonlyCollectionHandlers, readonlyMapを渡す
   ├─ 既にProxyの場合: reactiveオブジェクトのreadonly化は許可（例外処理）
   ├─ TargetType判定
   ├─ readonlyMap(WeakMap)で既存readonly Proxy検索
   └─ 新規readonly Proxy生成
3. readonlyMapに登録してプロキシを返却
```

### フローチャート

```mermaid
flowchart TD
    A[readonly target] --> B[createReactiveObject]
    B --> C{isObject target ?}
    C -->|No| D[警告して返却]
    C -->|Yes| E{既にProxy?}
    E -->|Yes| F{readonly + reactive?}
    F -->|Yes| G[続行]
    F -->|No| H[そのまま返却]
    E -->|No| G
    G --> I{TargetType?}
    I -->|INVALID| J[targetを返却]
    I -->|COMMON/COLLECTION| K{readonlyMap既存?}
    K -->|Yes| L[既存readonlyProxyを返却]
    K -->|No| M[new Proxy target, readonlyHandlers]
    M --> N[readonlyMapに登録]
    N --> O[readonly Proxyを返却]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-01 | 書き込み拒否 | set操作は常にtrueを返すが実際の値変更は行わない。開発モードで警告 | readonlyプロキシへのset時 |
| BR-02 | 削除拒否 | delete操作は常にtrueを返すが実際の削除は行わない。開発モードで警告 | readonlyプロキシへのdelete時 |
| BR-03 | 深い読み取り専用 | ネストされたオブジェクトもアクセス時にreadonly()で遅延変換 | readonly()使用時（深い） |
| BR-04 | reactive上のreadonly | reactiveオブジェクトをreadonly()でラップ可能。元の変更は反映される | reactiveオブジェクトをreadonly()に渡す場合 |
| BR-05 | 浅い読み取り専用 | shallowReadonly()はルートレベルのプロパティのみ読み取り専用 | shallowReadonly()使用時 |

### 計算ロジック

特になし

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

該当なし

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | 開発者警告 | readonlyプロキシへのset操作 | `Set operation on key "{key}" failed: target is readonly.` |
| - | 開発者警告 | readonlyプロキシへのdelete操作 | `Delete operation on key "{key}" failed: target is readonly.` |
| - | 開発者警告 | プリミティブ値をreadonly()に渡した場合 | `value cannot be made readonly: {value}` |

### リトライ仕様

該当なし

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

該当なし

## パフォーマンス要件

- readonlyMapによるProxy重複生成防止
- ネストオブジェクトの遅延readonly化

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

readonly()は意図しない状態変更を防止するためのAPI。ただし、toRaw()で元のオブジェクトを取得可能であるため、セキュリティ境界としては機能しない。

## 備考

- computed()のsetterが未定義の場合、内部的にIS_READONLYフラグが設定される
- `shallowReadonly()`はPropsハンドラで使用され、ルートレベルのプロパティのみ読み取り専用にする
- `isReadonly()`でreadonly状態を判定可能

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | reactive.ts | `packages/reactivity/src/reactive.ts` | DeepReadonly<T>型定義（156-176行目）。再帰的な読み取り専用型 |
| 1-2 | reactive.ts | `packages/reactivity/src/reactive.ts` | readonlyMap WeakMap（31-32行目）。readonlyプロキシのキャッシュ |
| 1-3 | constants.ts | `packages/reactivity/src/constants.ts` | ReactiveFlags.IS_READONLY（20行目） |

**読解のコツ**: readonly()はreactive()と同じcreateReactiveObject()を使用するが、引数として渡すisReadonlyフラグ、ハンドラ、キャッシュマップが異なる。この違いを意識して読むとよい。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | reactive.ts | `packages/reactivity/src/reactive.ts` | readonly()関数（208-218行目）。createReactiveObject()への委譲 |

**主要処理フロー**:
1. **208-218行目**: readonly()関数。isReadonly=true、readonlyHandlers、readonlyCollectionHandlers、readonlyMapを渡す
2. **261-302行目**: createReactiveObject()。reactive()と共通のProxy生成ロジック

#### Step 3: ReadonlyReactiveHandlerを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | baseHandlers.ts | `packages/reactivity/src/baseHandlers.ts` | ReadonlyReactiveHandler（225-249行目）。set/deletePropertyの拒否実装 |

**主要処理フロー**:
- **225-228行目**: ReadonlyReactiveHandlerクラス定義。BaseReactiveHandlerを継承しisReadonly=trueで初期化
- **230-238行目**: set()。警告を出してtrueを返す（実際の値変更なし）
- **240-248行目**: deleteProperty()。警告を出してtrueを返す（実際の削除なし）

#### Step 4: getハンドラの読み取り専用対応を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | baseHandlers.ts | `packages/reactivity/src/baseHandlers.ts` | BaseReactiveHandler.get()（55-134行目）。IS_READONLY判定とネストreadonly化 |

**主要処理フロー**:
- **62-63行目**: key===IS_READONLYの場合、isReadonlyフラグを返す
- **112-114行目**: readonlyの場合track()を呼ばない（読み取り専用の依存追跡は元オブジェクト経由）
- **126-131行目**: ネストオブジェクトのreadonly()遅延変換

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

```
readonly(target)
    |
    +-- createReactiveObject(target, true, readonlyHandlers, readonlyCollectionHandlers, readonlyMap)
            |
            +-- getTargetType(target)
            +-- readonlyMap.get(target)
            +-- new Proxy(target, readonlyHandlers)
            |       |
            |       +-- [get trap] BaseReactiveHandler.get() (isReadonly=true)
            |       |       +-- ネストオブジェクト -> readonly(res)
            |       |
            |       +-- [set trap] ReadonlyReactiveHandler.set()
            |       |       +-- 警告出力、値変更なし
            |       |
            |       +-- [deleteProperty trap] ReadonlyReactiveHandler.deleteProperty()
            |               +-- 警告出力、削除なし
            |
            +-- readonlyMap.set(target, proxy)
```

### データフロー図

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

target (object)  ───>  readonly(target)             ───>  DeepReadonly<T>
                           |
                    createReactiveObject()
                    (isReadonly=true)
                           |
                    readonlyHandlers適用
                           |
                    +------+------+
                    |             |
              GET access    SET/DELETE access
                    |             |
              値を返却       警告 + 変更なし
              (nested readonly)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| reactive.ts | `packages/reactivity/src/reactive.ts` | ソース | readonly()関数、shallowReadonly()、createReactiveObject共通ロジック |
| baseHandlers.ts | `packages/reactivity/src/baseHandlers.ts` | ソース | ReadonlyReactiveHandler。set/deleteの拒否実装 |
| collectionHandlers.ts | `packages/reactivity/src/collectionHandlers.ts` | ソース | readonlyCollectionHandlers。Map/Set向けreadonly |
| constants.ts | `packages/reactivity/src/constants.ts` | ソース | ReactiveFlags.IS_READONLY定義 |
| index.ts | `packages/reactivity/src/index.ts` | ソース | パッケージエクスポート定義 |
