[Vue3] オブジェクトをリアクティブにするのに ref()、reactive() どちらにするかハマった話

# はじめに

オブジェクト変数の内容を全入れ替えしたら値が反映されずハマってしまったので備忘録として残します。

下記のような条件です。

- Vue3 + TypeScript + &lt;script setup&gt; 構文
- オブジェクトの一部プロパティを更新するのではなく、**オブジェクト丸ごと更新する**

`<script setup>` 構文については下記の記事が参考になりました。

https://zenn.dev/azukiazusa/articles/676d88675e4e74


# リアクティブな変数を自分で定義する必要がある

`<script setup>` 構文ではリアクティブにする変数を `ref()` `reactive()` で明確にする必要があります。

例えば下記のコンポーネントでは数値は更新されません。

```vue
<script setup lang="ts">
let count = 0;

setInterval(() => {
  count += 1;
}, 1000);
</script>

<template>
  <div>
    {{ count }}
  </div>
</template>
```

画面に反映するには `ref()`、`reactive()` でリアクティブとして定義する必要があります。

```vue
<script setup lang="ts">
import { ref, reactive } from 'vue';

const count = ref(0);
const countObj = reactive({
  count: 0,
});

setInterval(() => {
  count.value += 1;
  countObj.count += 1;
}, 1000);
</script>

<template>
  <div>{{ count }} / {{ countObj.count }}</div>
</template>
```

## `ref()`、`reactive()` どちらを使うかは指定されていない

公式ではどちらを使うかは指定されていません。
そのため **プリミティブな値は `ref()`、オブジェクトは `reactive()`** みたいな記事がいくつかあります。
上記のサンプルでも `countObj` は `reactive()` を使っていますが、これもただの一例です。

`ref()`、`reactive()` の違いについては下記が参考になります。

https://zenn.dev/azukiazusa/articles/ref-vs-article


# オブジェクトが全部入れ替わるパターンでは……？

ユーザー情報をまるっと入れ替える例です。

## ダメな例

```vue
<script setup lang="ts">
import { reactive } from 'vue';

const user1 = {
  name: '鈴木',
  age: 20,
};

const user2 = {
  name: '佐藤',
  age: 21,
};

let user = reactive(user1);

// 1秒後に「佐藤」に入れ変えたいが、変わらない
setTimeout(() => {
  user = reactive(user2);
}, 1000);
</script>

<template>
  <div>
    名前：{{ user.name }}<br />
    年齢：{{ user.age }}
  </div>
</template>
```

なぜでしょう？
最初の変数宣言で `reactive()` をしています。

```vue
let user = reactive(user1);
```

しかしその後別の内容を代入したので `reactive()` を使っていますが、リンクが切れてしまいました。

```vue
setTimeout(() => {
  user = reactive(user2);
```

## 動作する例①

```vue
<script setup lang="ts">
import { reactive } from 'vue';

// ユーザー情報部分は同じなので省略

// user をラッピングした reactive なオブジェクト
const userRactive = reactive({
  user: user1,
});

setTimeout(() => {
  userRactive.user = user2;
}, 1000);
</script>

<template>
  <div>
    名前：{{ userRactive.user.name }}<br />
    年齢：{{ userRactive.user.age }}
  </div>
</template>
```

これなら表示は更新されます。
`userReactive` というリアクティブなオブジェクトの `user` プロパティだけ書き換えてるため、リンクが切れていないからです。

```vue
const userRactive = reactive({
  user: user1,
});
```

```vue
setTimeout(() => {
  userRactive.user = user2;
```

## 動作する例②

こちらも動作します。
`ref()` のほうがコードがすっきりします。

```vue
<script setup lang="ts">
import { ref } from 'vue';

// ユーザー情報部分は同じなので省略

const user = ref(user1);

setTimeout(() => {
  user.value = user2;
}, 1000);
</script>

<template>
  <div>
    名前：{{ user.name }}<br />
    年齢：{{ user.age }}
  </div>
</template>
```

# オブジェクトを `ref()` で扱う時はどうするか

`ref()` でリアクティブにした変数の値を変更するには `.value` を経由します。
これはオブジェクトも同じです。

```vue
<script setup lang="ts">
import { ref } from 'vue';

const user = ref({
  name: '鈴木',
  age: 20,
});

setTimeout(() => {
  user.value.age = 21;
}, 1000);
</script>

<template>
  <div>
    名前：{{ user.name }}<br />
    年齢：{{ user.age }}
  </div>
</template>
```

# まとめ

- `reactive()`、`ref()` どちらを使っても同じ結果は得られる
- 文字列、数値、真偽値などプリミティブな値は `ref()` を使う
- オブジェクトの**プロパティを変更するだけなら** `reactive()` でもOK
- オブジェクト内容をまるっと入れ替えるなら `ref()`

個人的にはオブジェクトも `ref()` で良いと思いました。

`ref()` のほうが汎用性が高いのと、`.value` があることでリアクティブだと判断しやすいことです。

もっと良い方法がありましたら教えていただけると嬉しいです。