LoginSignup
0
3

More than 1 year has passed since last update.

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

Last updated at Posted at 2022-07-26

最近、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の誤った使用例

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>
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が反映されない

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

ParentItem.vue
    setup() {
		// ref使用
        const value = ref({
            a: 0,
            b: '',
        });

valueが反映される

あるいは、reactiveの中でオブジェクトを宣言する。

ParentItem.vue
    setup() {
		// reactive内でオブジェクトを宣言
        const state = reactive({
            value: {
                a: 0,
                b: '',
            },
        });

つまり、reactiveというのは、引数に渡したオブジェクト内の各要素をリアクティブな値にするものであり、引数に渡したオブジェクト自体をリアクティブな値にするものではない。
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に軍配かな。

0
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
3