【Vue】reactive()って要らなくね？ref()だけでよくね？

## 使用技術

- Vue3 + Nuxt3
    - Composition API
- TypeScript

## ワイ、お問い合わせフォームを作りたい

ワイ「Vue の **Composition API** を使って、お問い合わせフォームを作るで！」
ワイ「フォームのデザインはこんな感じや！」

![スクリーンショット 2023-06-25 15.07.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/295452/575293f0-16a1-1544-1e16-f5e3e30d0971.png)

ワイ「いや〜、我ながらイケてるデザインやな！」
ワイ「ほな、Composition APIを使ってフォームの状態を管理していくで〜」



## フォームの状態を管理するには？

### `ref()`または`reactive()`
ワイ「Composition APIの場合、状態を管理するには」
ワイ「`ref()`または`reactive()`を使うんやったな」
ワイ「この2つは何が違うんやろ？」
ワイ「確か───」

- `ref()`
    - **文字列・数値・真偽値**などの、**プリミティブな値**を保持できる
- `reactive()`
    - **オブジェクト・配列・Map・Set**などの状態を保持できる

ワイ「↑こんな感じで使い分けるんやっけ・・・？」
ワイ「しばらく前に勉強したけど、もう忘れてもうたわ！」
ワイ「とりあえず、両方とも触ってみよか！」

## `ref()`を使ってフォームの状態を管理してみる

ワイ「さあ、コードを書いていくで！」

### script部分

```vue
<script setup lang="ts">
// お問い合わせ内容の状態を管理
const contentRef = ref("");
</script>
```

ワイ「おお、TypeScript部分はたったこれだけでええんか[^1]」

### template部分

```vue
<template>
  <form>
    <h1>お問い合わせフォーム</h1>
    <dl>
      <dt>お問い合わせ内容</dt>
      <dd>
        <textarea type="text" name="content" v-model="contentRef" />
      </dd>
    </dl>
    <button>送信</button>
  </form>
</template>
```

ワイ「テンプレート部分は↑こんな感じやな」
ワイ「おなじみ`v-model`を使って**双方向バインディング**してやる感じやな」

### 結果

![スクリーンショット 2023-06-25 15.10.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/295452/e3af97dd-76ce-1c93-275a-79c761763ac2.png)


### できた

ワイ「おお、できたで！」
ワイ「入力した内容が、ちゃんと下側の部分に反映されとる」
ワイ「`ref()`を使って、文字列の状態を管理することができたで！」

### リセット機能もつけたい

ワイ「入力内容をリセットするボタンもつけたいな」
ワイ「追加してみよか」

```diff_typescript
    // お問い合わせ内容の状態を管理
    const contentRef = ref("");

+   // フォームの状態をリセットする関数
+   const resetContentRef = () => {
+     contentRef.value = ""
+   };
```

ワイ「↑こうやな」
ワイ「`ref()`で管理してる値にアクセスするには」
ワイ「`.value`って書いてやらなアカンのやな」

※`<template>`内では`.value`を書く必要はありません

ワイ「ほんで、html も追加してやらんとな」

```diff_vue
<template>
  <form>
    <h1>お問い合わせフォーム</h1>
    <dl>
      <dt>お問い合わせ内容</dt>
      <dd>
        <textarea type="text" name="content" v-model="contentRef" />
      </dd>
    </dl>
    <button>送信</button>
+   <button type="button" @click="resetContentRef">リセット</button>
  </form>
</template>
```

ワイ「↑こうやな」

ワイ「ほな、リセットボタンをポチ！」

### 結果

![スクリーンショット 2023-06-25 15.25.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/295452/ec054cb4-f07e-c5fb-4f82-320b4733e143.png)

ワイ「お、ちゃんと空っぽになったで」
ワイ「むっちゃ簡単やな」

ワイ「ほな、次は`reactive()`を使って同じことをしてみよか」

## `reactive()`を使ってフォームの状態を管理してみる

ワイ「ほな`reactive()`を使って書いてみるで」

```vue
<script setup lang="ts">
// お問い合わせ内容の状態を管理
const contentReactive = reactive("");
</script>
```

ワイ「あれ？」

![スクリーンショット 2023-06-25 15.17.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/295452/fcf21234-a716-688d-39a2-e92d4c2dbaac.png)


ワイ「VSCodeに、**赤い波線**が表示されとる・・・」
ワイ「TypeScriptのエラーが出てもうたみたいや」

> **エラー内容**
> 型 'number' の引数を 型 'object' のパラメーターに割り当てることはできません。

ワイ「？？？」
ワイ「つまり・・・？」

## `reactive()`では、プリミティブ型の値を保持できない

ワイ「なるほどな」
ワイ「`reactive()`の場合は、**文字列・数値・真偽値**などの、**プリミティブな値**を保持できひんのやな」
ワイ「`reactive()`は、**オブジェクト・配列・Map・Set**などの状態を管理するためのAPIなんやな」

### なので、オブジェクトにする

ワイ「プリミティブ値がダメなら」
ワイ「こう、例えば`value`というプロパティを持った**オブジェクト**にしてやれば」
ワイ「`reactive()`で状態を管理できるな」

```diff_typescript
    // お問い合わせ内容の状態を管理
    const contentReactive = reactive({ value: "" });
```

ワイ「ほんで、リセットボタンは───」

```diff_typescript
    // フォームの状態をリセットする関数
    const resetContentReactive = () => {
      contentReactive.value = "";
    };
```

ワイ「↑こうやな」

## なんか、`ref()`と同じ感じになった

ワイ「結局、`ref()`を使った場合とほとんど同じ感じになったな」
ワイ「`reactive()`ではプリミティブ値を保持できないから」
ワイ「わざわざ`value`というプロパティを持ったオブジェクトを定義したけど」
ワイ「それなら普通に`ref()`を使えばいいかもな」
ワイ「今のところ、`ref()`と`reactive()`の違いがあんまり分からへんな・・・」

ワイ「とりあえず続きを実装していこか」

## Next: メールアドレス入力欄が必要

ワイ「そういえば、メールアドレスの入力欄が必要や」
ワイ「そうやないと、お問い合わせに返信できひんわ」
ワイ「ほな、コードを修正していくで」


## まずは`ref()`を使ってやってみる

ワイ「さっきは、お問い合わせ内容の本文だけだったから**プリミティブ値**だったけど」
ワイ「今度はメールアドレスも必要やから───」

```diff_typescript
-   const contentRef = ref("");
+   const formRef = ref({
+     email: "",
+     content: "",
+   });
```

ワイ「↑こうやな」
ワイ「`email`と`content`というプロパティを持った**オブジェクト**に変えてみたで」

ワイ「`ref()`はプリミティブ値専用というわけじゃなくて」
ワイ「**オブジェクトの状態も管理できる**んやな」
ワイ「知らんかったわ」

## リセット用の関数も修正

ワイ「フォームを空っぽにするわけやから───」

```diff_typescript
    // フォームの状態をリセットする関数
    const resetFormRef = () => {
      // フォームの状態を上書き
      formRef.value = {
        email: "",
        content: "",
      };
    };
```

ワイ「↑こうやな！」
ワイ「簡単や！」

## 今度は`reactive()`でやってみる

ワイ「お問い合わせ内容とメールアドレスの状態を管理したいから───」

```diff_typescript
-   const contentReactive = reactive({ value: "" });
+   let formReactive = reactive({
+     email: "",
+     content: "",
+   });
```

ワイ「↑こうやな！」

ワイ「ほんで、リセットボタン用の関数も書き直さんとな」

```diff_typescript
    // フォームの状態をリセットする関数
    const resetContentReactive = () => {
      // フォームの状態を上書き
      formReactive = {
        email: "",
        content: "",
      };
    };
```

ワイ「↑こうやな」

ワイ「ほな、リセットボタンをポチ！」

### 結果

![スクリーンショット 2023-06-25 16.26.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/295452/449b7b4f-6715-d47b-41b4-a71550fd0de1.png)

ワイ「あれ、消えてへん・・・・」

## `reactive()`の場合、オブジェクト全体を上書きできない

ワイ「なるほど、`reactive()`の場合は**丸ごと置き換え**はできひんのか」
ワイ「ほな───」

```diff_typescript
    // フォームの状態をリセットする関数
    const resetFormReactive = () => {
      // 1プロパティずつ上書き
      formReactive.email = "";
      formReactive.content = "";
    };
```

ワイ「↑こうせなアカンのやな」
ワイ「プロパティ1つ1つに対して空の値を入れてやらなアカンわけやな」
ワイ「プロパティが沢山ある場合には面倒そうやな」
ワイ「`ref()`と違って`.value`を書かなくていいのは楽やけどな」

### もしくは、オブジェクト全体を`value`プロパティに入れる[^2]

ワイ「もしくは、管理したい全ての状態を───」

```diff_typescript
    const formReactive = reactive({
      value: {
        email: "",
        content: "",
      }
    });
```

ワイ「↑こう、`value`プロパティとかで包んでやれば」

```diff_typescript
    // フォームの状態をリセットする関数
    const resetFormReactive = () => {
      // フォームの状態を上書き
      formReactive.value = {
        email: "",
        content: "",
      };
    };
```

ワイ「↑こう、丸ごと上書きできるわけやな」

### でも、その場合`<template>`内で`.value`を省略できない

ワイ「`ref()`を使った場合、`<template>`内では`.value`を書かんくてよかったけど」
ワイ「`reactive()`の中に自分で`value`プロパティを定義した場合は───」

```vue
    <dl>
      <dt>メールアドレス</dt>
      <dd>
        <input type="text" name="email" v-model="formReactive.value.email">
      </dd>
      <dt>お問い合わせ内容</dt>
      <dd>
        <textarea type="text" name="content" v-model="formReactive.value.content" />
      </dd>
    </dl>
```

ワイ「↑こう、`<template>`の中でも`.value`って書かないといけないんやな」

ワイ「これなら`ref()`を使ったほうがいい気がするな」

## 他にも、`reactive()`には制限が

### 分割代入NG

```ts
const { email, content } = formReactive;
```

↑こう、分割代入してしまうとリアクティビティが失われます。
つまり、**値を更新しても画面と連動しなく**なります。

※`toRefs()`を使って、全プロパティを`Ref`に変換してやれば回避できます

### でも、そこは`ref()`も同じようなもの

```ts
const { email, content } = formRef.value;
```

↑このように`ref.value`を分割代入した場合も、リアクティビティが失われます。

※この場合も`toRefs()`を使えばリアクティビティを保持できます


## `ref()`と`reactive()`の違いまとめ

### `ref()`

- メリット
    - どんな型の値でも保持できる
        - プリミティブ値もOK
    - 状態を丸ごと上書きできる
- デメリット
    - `.value`って書かないといけない
        - でも`<template>`の中では書かなくてOK

### `reactive()`

- メリット
    - `<script>`内で何度も`.value`を書かなくていい
- デメリット
    - プリミティブ値を管理できない
        - オブジェクトのプロパティとしてなら保持できる
    - 状態を丸ごと上書きできない
        - プロパティ1つずつ上書きする必要がある
        - オブジェクトのプロパティなら丸ごと上書き可能

### 共通
- 分割代入するときは注意が必要
    - リアクティビティを保持したい場合、`toRefs()`を使うべし


## `ref()`だけでよくね？`reactive()`要らなくね？

`reactive()`には「プリミティブ値を管理できない」「状態を丸ごと上書きできない」といった制限があるため、割と使いづらく感じました。

`null`も管理できないため、`object | null`的な型も使用できません。

正直「`ref()`だけでよくね？`reactive()`要らなくね？」と思いました。
「似たような機能が2つあるより、1つしかない方がいいんじゃ・・・？」と。

「使い分けできる」というメリットよりも
「両方キャッチアップして吟味しなければならない」という**デメリット**の方が大きく感じました。
人によって、どちらを使うか「揺れ」も生じますし。

### でも、`ref()`だと何度も`.value`を書かなくちゃいけないじゃん？

確かにそこは手間ではありますが、
逆にコードは読みやすくなりそうだと感じました。

### `ref()`の場合

```ts
obj.value.prop1 = "";
```

↑「`.value`経由で値を再代入しているから、これは`ref()`で管理している状態だな！」
と推測できます。

さらに、TypeScriptを使用していれば、`ref()`で作った値だということが型情報としても可視化されます。

![スクリーンショット 2023-06-25 17.29.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/295452/57c6463b-d65f-9555-5533-cf08301e704f.png)


### `reactive()`の場合

```ts
obj.prop1 = "";
```

↑`reactive()`で状態管理しているオブジェクトなのか、それとも「ただのオブジェクト」なのか見分けがつきません。

### 型情報を見てみても・・・

VSCodeで型情報を見てみても、`reactive()`で作ったオブジェクトであることは分かりませんでした。

![スクリーンショット 2023-06-25 17.24.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/295452/315ec4f4-c25b-ee62-2660-90d0d546914e.png)


## 公式にも`ref()`推奨らしい

[公式ドキュメント](https://ja.vuejs.org/guide/essentials/reactivity-fundamentals.html#limitations-of-reactive)にも「主に`ref()`を使うように」という旨が記載されていました。


> `reactive()`APIにはいくつかの制限があります
>
> （中略）
>
> このような制約があるため、リアクティブな状態を宣言するための主要な API として
`ref()`を使用することを推奨します。

**みなさん、基本的に`ref()`を使っていきましょう。**

## 【おまけ】 実は`ref()`の中で、内部的に`reactive()`が使われている

非プリミティブ値（オブジェクトなど）を`ref()`に渡した場合、内部的に`reactive()`が使われます。
なので「`reactive()`は完全に不要」ということにはなりません。
ただし、ユーザから使えるAPIとしては要らないかなって思いました。

## 参考文献

- [リアクティビティーの基礎 | Vue.js](https://ja.vuejs.org/guide/essentials/reactivity-fundamentals.html)

## 新しい記事もよろしくやで！

- [7歳娘「パパ、TypeScriptのsatisfiesって便利だね！」](https://qiita.com/Yametaro/items/494c6e69f7e9bede2197)
- [親コンポーネントがステートを持つべきなの？それとも子コンポーネント？](https://qiita.com/Yametaro/items/e812377c380a7fe5a203)

[^1]: Nuxt3を使用しているため、`ref`のimportは不要になっています。
[^2]: プロパティ名は別に`value`じゃなくてもいいです