【Vue3】reactiveは『オブジェクトをリアクティブな値にするもの』ではない

最近、Vue3 (Composition API)のreactiveがどういったものなのか、ようやくわかったので整理。

## refとreactive

Vue3(Composition API)でリアクティブな値を用意する方法として、以下2通りがある。

- refを使用
    
    ```tsx
    const a = ref(0);
    const b = ref('A');
    ```
    
- reactiveを使用
    
    ```tsx
    const state = reactive({
    	a: 0,
    	b: 'A',
    });
    ```
上述の２例が、ほぼ同じことを実現している。

## reactiveの注意点

気を付けたいのは、
**「reactiveは『オブジェクトをリアクティブな値にするもの』ではない」**
ということ。

オブジェクトをリアクティブな値にするならば、以下のように書く。

- refを使用
    
    ```tsx
    const val = ref({
		a: 0,
		b: 'A',
	});
    ```
    
- reactiveを使用
    
    ```tsx
    const state = reactive({
    	val: {
			a: 0,
			b: 'A',
		},
    });
    ```
上述の２例が、ほぼ同じことを実現しており、
対して、以下のようなreactiveの書き方は誤り。

```tsx
const val= reactive({
		a: 0,
		b: 'A',
});
```

こう書いてしまうと、以下のようにオブジェクト自体を更新するケースでうまく動作しない。

### reactiveの誤った使用例

```jsx:ParentItem.vue
<template>
    <div>
        {{value}}
        <child-item v-model="value" />
    </div>
</template>

<script>
import { defineComponent, reactive} from 'vue';
import ChildItem from './ChildItem.vue';

export default defineComponent({
    components: { ChildItem },
    setup() {
		// reactiveを使用
        const value = reactive({
            a: 0,
            b: '',
        });
        return {
            value,
        };
    },
});
</script>
```

```jsx:ChildItem.vue
<template>
    <div>
        <input type="number" v-model="valueA" />
        <input type="text" v-model="valueB" />
    </div>
</template>

<script>
import { defineComponent, PropType, ref, watch } from 'vue';

export default defineComponent({
    props: {
        modelValue: {
            type: Object,
            required: true,
        },
    },
    emits: ['update:modelValue'],
    setup(props, { emit }) {
        const valueA = ref(0);
        const valueB = ref('A');

        watch(() => props.modelValue, (val) => {
            valueA.value = val.a;
            valueB.value = val.b;
        }, { immediate: true });

        watch(() => [valueA.value, valueB.value], ([newValA, newValB]) => {
            emit('update:modelValue', {
                a: newValA,
                b: newValB,
            });
        });
        return {
            valueA,
            valueB,
        }
    },
});
</script>
```

ChildItemで値を更新しても、ParentItem内のvalueに反映されない。
![valueが反映されない](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/236394/ea3176b0-6b99-7e7c-3ce1-89dee9aaea77.png "valueが反映されない")

ParentItem内でrefを使ってvalueを宣言するように修正すれば、問題なく動作する。

```jsx:ParentItem.vue
    setup() {
		// ref使用
        const value = ref({
            a: 0,
            b: '',
        });
```
![valueが反映される](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/236394/a9bb9b00-710e-1223-29f0-f79018072565.png "valueが反映される")

あるいは、reactiveの中でオブジェクトを宣言する。
```jsx:ParentItem.vue
    setup() {
		// reactive内でオブジェクトを宣言
        const state = reactive({
            value: {
                a: 0,
                b: '',
            },
        });
```
つまり、reactiveというのは、引数に渡した**オブジェクト内の各要素をリアクティブな値にするもの**であり、引数に渡したオブジェクト自体をリアクティブな値にするものではない。
![reactiveとrefの違い](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/236394/87ce2c9c-1e4f-750c-da8c-20fea8c3aff3.png "reactiveとrefの違い")


## 個人的見解

「リアクティブな値を用意する」という同じ役割を持ったものが、refとreactiveの2方式あるのは、個人的には、あまりよろしくない仕様だと思う。使い手に混乱を与える。
ただ、refとreactiveの2方式ある背景は、おそらく、
- Vue2でのリアクティブな値の管理方式がreactiveに近い（Vue2から移植がしやすい）
- フックの活用を考えるとrefの形が望ましい

という二極の狭間でVue制作者たちが悩み続けて、結局、どちらにも振り切れなかったからなのではないかと思う。

refとreactiveの両方を色々試してみた結果、個人的には、
「リアクティブな値はrefを使用する」
という方針に落ち着いている。

- refに統一する利点
    - リアクティブな値か、そうでない値かが、明確にわかる。（refに統一しておけば、「`.value`でアクセスしているもの＝リアクティブな値」となる）
    - 後からフックに処理を切り出しやすい。
    - reactiveが不要になる。（DOMや子コンポーネントへのアクセスにrefは必要なので、「reactiveのみに統一」はできない）

Vue2からの移植しやすさ以外でのreactiveの利点を挙げるなら、
コンポーネント内のリアクティブな値を、`const stateA = reactive(…); const stateB = reactive(…);`みたいにグルーピングできることかなぁ…、
と考えたりもしたのだけど、グルーピングしなければわかりづらいような状態になったなら、コンポーネントかフックで切り出すのが正だと思うので、やっぱりrefに軍配かな。