😇

Vue refとreactiveでゴニョゴニョしてたら謎挙動起きて沼った

2023/11/04に公開

はじめに

少し前に、refとreactiveの挙動で、「え、こんな動きするんや。そうなんや〜」ってなった話を書きます。
初心者級な話です。公式ちゃんと読めよって話です。ちゃんと読んでなくてすいません。

改めてリアクティブとは

改めてリアクティブについて調べましたが、
リアクティブとは、「その値が監視され、変更が検知される状態」とのこと。
リアクティブな値は、変更を検知して、変更が都度更新されるんですよね〜。VueとかReact使ってたら、リアクティブな値は使いまくります。

普通のref

まずは、普通のrefから、
例えば、こんな感じで書きますよね。

コード

<script setup lang="ts">
import { ref, reactive } from 'vue';
const refValue = ref<string>('');
</script>

<template>
  <div class="form-row">
    <input v-model="refValue">
    <p>refValue: {{refValue}}</p>
  </div>
</template>

挙動

まあ普通ですね
ちなみに公式によると、Composition APIは、refを使うことを推奨しているとのことです。

普通のreactive

reactiveは、オブジェクト自体をラップします。

コード

<script setup lang="ts">
import { ref, reactive } from 'vue';
const reactiveValue = reactive(
  {
    comment: ''
  }
);
</script>
<template>
  <div class="form-row">
    <input v-model="reactiveValue.comment">
    <p>reactiveValue: {{reactiveValue.comment}}</p>
  </div>
</template>

挙動


これも普通です

refをオブジェクトでラップする

ここからちょっと初めて知ることがあるかもです。

コード

refをオブジェクトにラップしただけです。

<script setup lang="ts">
import { ref } from 'vue';
const refValue = ref<string>('');
const objectInRef = {
  comment: refValue
}
</script>
<template>
  <div class="form-row">
    <input v-model="objectInRef.comment">
    <p>reactiveValue: {{objectInRef.comment}}</p>
  </div>
</template>

挙動

ちょっと今までと違う動きをします。

リアクティブな値が取れてないんですよね。
表示されてる値を見ると、.valueがないからミスってるように見えます。

objectInRef.comment.valueとやってみると、、

リアクティブな値が取れました!
オブジェクトでラップすると、template内でも、refの値に.valueをつけないといけない」
ってことですね。これは初めて知りました。

refをreactiveでラップする

reactiveでrefを囲ってみましょう。

コード

<script setup lang="ts">
import { reactive, ref } from 'vue';
const refValue = ref<string>('');
const reactiveInRef = reactive(
  {
    comment: refValue
  }
)
</script>
<template>
  <div class="form-row">
    <input v-model="reactiveInRef.comment">
    <p>reactiveValue: {{reactiveInRef.comment}}</p>
  </div>
</template>

挙動

reactiveで囲むと、オブジェクト全体がリアクティブになってくれるので、.valueなしでリアクティブな値が取れます。

公式を読んでみる

Vueの公式ドキュメントを見ると、こんな記述がありました。
https://ja.vuejs.org/guide/essentials/reactivity-fundamentals.html#additional-ref-unwrapping-details

ref は、リアクティブなオブジェクトのプロパティとしてアクセスまたは変更されると、自動的にアンラップされます。つまり、通常のプロパティと同じように動作します:

どうやら、

  • refValue自体のリアクティブが解除される。
  • reactiveでラップしているので、reactiveInRef全体がリアクティブになっている
  • 結果として、reactiveInRef.commentで、リアクティブな値が取れる

と言うことになってるみたい。

refをreactiveでラップして、refの値を更新してみる

ややこしくなってますが、もう少しややこしいことしてみます。
refValueの値を更新・reactiveValueの値を更新したら、互いに変更を検知してくれるのかみてみたいと思います。

コード

<script setup lang="ts">
import { reactive, ref } from 'vue';
const refValue = ref<string>('');
const reactiveInRef = reactive(
  {
    comment: refValue
  }
)
</script>
<template>
  <div class="form-row">
    <input v-model="refValue">
    <p>refValue: {{refValue}}</p>
  </div>
  <div class="form-row">
    <input v-model="reactiveInRef.comment">
    <p>reactiveValue: {{reactiveInRef.comment}}</p>
  </div>
</template>

挙動


更新されますね〜

refValue自体はリアクティブなので、更新されるのはわかるんですが、reactiveでラップしたcomment: refValueも、reactiveInRef全体がリアクティブだから、更新されるってことなんですかね。

同じ値が、refValueと、reactive.commentのどちらにも同じ値が存在してしまって混乱しそうなので、こう言う使い方は避けた方が良さそう。

今日はここらへんにしておきます。

まとめ

軽い気持ちで、refの値をオブジェクトで1つでまとめようとしたら、謎の挙動が起きて色々気づきを得ました。
公式ドキュメントちゃんと読めって話ですね。

どうしてもrefをオブジェクトでまとめたいなら、reactiveでまとめてしまうのが一番マシそう。
ただ混乱しそうな要素があるので、あまりやりたくないですね。

リアクティブな配列も普通の配列とちょっと挙動違うんで、今度調べます〜

背伸びして基礎的なことを甘く見てしまいますが、
やっぱり公式で基礎をしっかり学習するのが一番だなーと思いました。

引用

https://ja.vuejs.org/guide/essentials/reactivity-fundamentals.html

Discussion