Vue.js 3 ref reactive 早見表

# 早見表


|        | ref()                     | reactive()               |
|--------------------|----------------------------|---------------------------|
| データ型           | 全ての型 | オブジェクト、配列、Mapなど |
| 分割代入           | サポートされない         | サポートされない         |
| スクリプト内でのアンラップ | .value プロパティが必要  | 自動的にアンラップ       |

# どっちを使えばいいか
[公式](https://ja.vuejs.org/guide/essentials/reactivity-fundamentals.html)にもあるように、現在(2023/10)時点では`ref()`が推奨されている。
(`reactive()`にはいくつかの制限があるため)

> リアクティブな状態を宣言するための主要な API として ref() を使用することを推奨します。

# メリット・デメリット
## ref()

メリット
- どんな型の値でも保持できる
- `script`内では`.value`で参照するので、リアクティブな値だと明示的に示せる
- 状態を丸ごと上書きできる

デメリット
- `script`内で、データを扱う際`.value`を常に使用する必要があるため冗長に見えるという意見がある(個人的には、リアクティブな値と初見でわかるのでデメリットには感じません)

## reactive()

メリット
-  `script`内で`.value`を記述する必要がないので、直接的にデータを操作できる

デメリット
- 全ての型の値に対応していない
- 状態を丸ごと上書きできない

# データ型

## プリミティブ型(string,number,boolean...)

### ref()

:::note warn
script内では、.valueが必要です。
:::

```vue
<script setup>
import { ref } from "vue";

const level = ref(20);

const levelUp = () => {
  level.value++;
};
</script>

<template>
  <p>{{ level }}</p>
  <button @click="level++">Level up</button>
  <button @click="levelUp">Level up</button>
</template>
```

### reactive()

```vue
❌
```

実際は、下記のようにオブジェクトの中にプリミティブな値を持つことは可能ですが、refを使用するべきだと思います。

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

const level = reactive({ value: 20 });

const levelUp = () => {
  level.value++;
};
</script>

<template>
  <p>{{ level.value }}</p>
  <button @click="level.value++">Level up</button>
  <button @click="levelUp">Level up</button>
</template>
```


## オブジェクト

### ref()

:::note warn
script内では、.valueが必要です。
:::

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

const pokemon = ref({
  name: "ピカチュウ",
  type: "電気",
  level: 20,
});

const levelUp = () => {
  pokemon.value.level++;
};
</script>

<template>
  <p>{{ pokemon.name }} - {{ pokemon.type }} - {{ pokemon.level }}</p>
  <button @click="levelUp">Level up</button>
</template>
```

### reactive()

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

const pokemon = reactive({
  name: "ピカチュウ",
  type: "電気",
  level: 20,
});

const levelUp = () => {
  pokemon.level++;
};
</script>

<template>
  <p>{{ pokemon.name }} - {{ pokemon.type }} - {{ pokemon.level }}</p>
  <button @click="levelUp">Level up</button>
</template>
```

## 配列

### ref()

:::note warn
script内では、.valueが必要です。
:::

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

const pokemons = ref(["ピカチュウ", "リザードン", "フシギバナ"]);

const addPokemon = (newPokemon: string) => {
  pokemons.value.push(newPokemon);
};
</script>

<template>
  <ul>
    <li v-for="pokemon in pokemons" :key="pokemon">{{ pokemon }}</li>
  </ul>
  <button @click="addPokemon('カメックス')">カメックス ゲット</button>
</template>
```

### reactive()

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

const pokemons = reactive(["ピカチュウ", "リザードン", "フシギバナ"]);

const addPokemon = (newPokemon: string) => {
  pokemons.push(newPokemon);
};
</script>

<template>
  <ul>
    <li v-for="pokemon in pokemons" :key="pokemon">{{ pokemon }}</li>
  </ul>
  <button @click="addPokemon('カメックス')">カメックス ゲット</button>
</template>

```

# 分割代入

### ref()

:::note warn
下記のコードは、一部リアクティブに動作しません。
:::

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

const pokemon = ref({
  name: "ピカチュウ",
  type: "電気",
  level: 20,
});

const { name, type, level } = pokemon.value;
</script>

<template>
  <!-- リアクティブ -->
  <p>名前: {{ pokemon.name }}</p>
  <p>タイプ: {{ pokemon.type }}</p>
  <p>レベル: {{ pokemon.level }}</p>
  <button @click="pokemon.level++">Level up</button>

  <!-- 非リアクティブ -->
  <p>名前: {{ name }}</p>
  <p>タイプ: {{ type }}</p>
  <p>レベル: {{ level }}</p>
  <button @click="level++">Level up</button>
</template>

```

↓ toRefsを利用することで、リアクティブに利用することができます

```diff_vue
<script setup lang="ts">
- import { ref } from "vue";
+ import { ref, toRefs } from "vue";

const pokemon = ref({
  name: "ピカチュウ",
  type: "電気",
  level: 20,
});

- const { name, type, level } = pokemon.value;
+ const { name, type, level } = toRefs(pokemon.value);
</script>

<template>
  <p>名前: {{ pokemon.name }}</p>
  <p>タイプ: {{ pokemon.type }}</p>
  <p>レベル: {{ pokemon.level }}</p>
  <button @click="pokemon.level++">Level up</button>

+   <!-- こちらの上下のコードは同じ値が出力されます -->

  <p>名前: {{ name }}</p>
  <p>タイプ: {{ type }}</p>
  <p>レベル: {{ level }}</p>
  <button @click="level++">Level up</button>
</template>
```

### reactive()


:::note warn
下記のコードは、一部リアクティブに動作しません。
:::

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

const pokemon = reactive({
  name: "ピカチュウ",
  type: "電気",
  level: 20,
});

const { name, type, level } = pokemon;
</script>

<template>
  <!-- リアクティブ -->
  <p>名前: {{ pokemon.name }}</p>
  <p>タイプ: {{ pokemon.type }}</p>
  <p>レベル: {{ pokemon.level }}</p>
  <button @click="pokemon.level++">Level up</button>

  <!-- 非リアクティブ -->
  <p>名前: {{ name }}</p>
  <p>タイプ: {{ type }}</p>
  <p>レベル: {{ level }}</p>
  <button @click="level++">Level up</button>
</template>
```

↓ toRefsを利用することで、リアクティブに利用することができます


```diff_vue
<script setup lang="ts">
- import { reactive } from "vue";
+ import { reactive, toRefs } from "vue";

const pokemon = reactive({
  name: "ピカチュウ",
  type: "電気",
  level: 20,
});

- const { name, type, level } = pokemon;
+ const { name, type, level } = toRefs(pokemon);
</script>

<template>
  <p>名前: {{ pokemon.name }}</p>
  <p>タイプ: {{ pokemon.type }}</p>
  <p>レベル: {{ pokemon.level }}</p>
  <button @click="pokemon.level++">Level up</button>

+ <!-- こちらの上下のコードは同じ値が出力されます -->

  <p>名前: {{ name }}</p>
  <p>タイプ: {{ type }}</p>
  <p>レベル: {{ level }}</p>
  <button @click="level++">Level up</button>
</template>
```

# 状態を上書き

### ref()

`ref()`では下記のように、`pokemon.value`に直接上書きすることが可能

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

const pokemon = ref({
  name: "ピカチュウ",
  level: 20,
});

const changePokemon = () => {
  pokemon.value = { name: "カメックス", level: 40 };
};
</script>

<template>
  <p>{{ pokemon.name }}</p>
  <p>{{ pokemon.level }}</p>
  <button @click="changePokemon()">カメックスと交換</button>
</template>
```

### reactive()

`ref()`と同じようにオブジェクトを上書きしようとすると、エラーで怒られます。
reactiveはオブジェクトなどを、直接上書きできない。

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

const pokemon = reactive({
  name: "ピカチュウ",
  level: 20,
});

const changePokemon = () => {
  pokemon = { name: "カメックス", level: 40 };
};
</script>

<template>
  <p>{{ pokemon.name }}</p>
  <p>{{ pokemon.level }}</p>
  <button @click="changePokemon()">カメックスと交換</button>
</template>
```

```javascript:❗️error
pokemon = { name: "カメックス", level: 40 };
// error: Cannot assign to 'pokemon' because it is a constant.
```

### 解決策

プロパティ毎に、上書きする

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

const pokemon = reactive({
  name: "ピカチュウ",
  level: 20,
});

const changePokemon = () => {
- pokemon = { name: "カメックス", level: 40 };
+ pokemon.name = "カメックス";
+ pokemon.level = 40;
};
</script>

<template>
  <p>{{ pokemon.name }}</p>
  <p>{{ pokemon.level }}</p>
  <button @click="changePokemon()">カメックスと交換</button>
</template>
```

# 最後に
ご指摘・ご意見などありましたら、コメントして頂けると嬉しいです！