最近、Vue3 (Composition API)のreactiveがどういったものなのか、ようやくわかったので整理。
refとreactive
Vue3(Composition API)でリアクティブな値を用意する方法として、以下2通りがある。
-
refを使用
const a = ref(0); const b = ref('A');
-
reactiveを使用
const state = reactive({ a: 0, b: 'A', });
上述の2例が、ほぼ同じことを実現している。
reactiveの注意点
気を付けたいのは、
「reactiveは『オブジェクトをリアクティブな値にするもの』ではない」
ということ。
オブジェクトをリアクティブな値にするならば、以下のように書く。
-
refを使用
const val = ref({ a: 0, b: 'A', });
-
reactiveを使用
const state = reactive({ val: { a: 0, b: 'A', }, });
上述の2例が、ほぼ同じことを実現しており、
対して、以下のようなreactiveの書き方は誤り。
const val= reactive({
a: 0,
b: 'A',
});
こう書いてしまうと、以下のようにオブジェクト自体を更新するケースでうまく動作しない。
reactiveの誤った使用例
<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>
<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に反映されない。
ParentItem内でrefを使ってvalueを宣言するように修正すれば、問題なく動作する。
setup() {
// ref使用
const value = ref({
a: 0,
b: '',
});
あるいは、reactiveの中でオブジェクトを宣言する。
setup() {
// reactive内でオブジェクトを宣言
const state = reactive({
value: {
a: 0,
b: '',
},
});
つまり、reactiveというのは、引数に渡したオブジェクト内の各要素をリアクティブな値にするものであり、引数に渡したオブジェクト自体をリアクティブな値にするものではない。
個人的見解
「リアクティブな値を用意する」という同じ役割を持ったものが、refとreactiveの2方式あるのは、個人的には、あまりよろしくない仕様だと思う。使い手に混乱を与える。
ただ、refとreactiveの2方式ある背景は、おそらく、
- Vue2でのリアクティブな値の管理方式がreactiveに近い(Vue2から移植がしやすい)
- フックの活用を考えるとrefの形が望ましい
という二極の狭間でVue制作者たちが悩み続けて、結局、どちらにも振り切れなかったからなのではないかと思う。
refとreactiveの両方を色々試してみた結果、個人的には、
「リアクティブな値はrefを使用する」
という方針に落ち着いている。
- refに統一する利点
- リアクティブな値か、そうでない値かが、明確にわかる。(refに統一しておけば、「
.value
でアクセスしているもの=リアクティブな値」となる) - 後からフックに処理を切り出しやすい。
- reactiveが不要になる。(DOMや子コンポーネントへのアクセスにrefは必要なので、「reactiveのみに統一」はできない)
- リアクティブな値か、そうでない値かが、明確にわかる。(refに統一しておけば、「
Vue2からの移植しやすさ以外でのreactiveの利点を挙げるなら、
コンポーネント内のリアクティブな値を、const stateA = reactive(…); const stateB = reactive(…);
みたいにグルーピングできることかなぁ…、
と考えたりもしたのだけど、グルーピングしなければわかりづらいような状態になったなら、コンポーネントかフックで切り出すのが正だと思うので、やっぱりrefに軍配かな。