11
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

[Vue3] オブジェクトをリアクティブにするのに ref()、reactive() どちらにするかハマった話

Last updated at Posted at 2022-02-12

はじめに

オブジェクト変数の内容を全入れ替えしたら値が反映されずハマってしまったので備忘録として残します。

下記のような条件です。

  • Vue3 + TypeScript + <script setup> 構文
  • オブジェクトの一部プロパティを更新するのではなく、オブジェクト丸ごと更新する

<script setup> 構文については下記の記事が参考になりました。

リアクティブな変数を自分で定義する必要がある

<script setup> 構文ではリアクティブにする変数を ref() reactive() で明確にする必要があります。

例えば下記のコンポーネントでは数値は更新されません。

<script setup lang="ts">
let count = 0;

setInterval(() => {
  count += 1;
}, 1000);
</script>

<template>
  <div>
    {{ count }}
  </div>
</template>

画面に反映するには ref()reactive() でリアクティブとして定義する必要があります。

<script setup lang="ts">
import { ref, reactive } from 'vue';

const count = ref(0);
const countObj = reactive({
  count: 0,
});

setInterval(() => {
  count.value += 1;
  countObj.count += 1;
}, 1000);
</script>

<template>
  <div>{{ count }} / {{ countObj.count }}</div>
</template>

ref()reactive() どちらを使うかは指定されていない

公式ではどちらを使うかは指定されていません。
そのため プリミティブな値は ref()、オブジェクトは reactive() みたいな記事がいくつかあります。
上記のサンプルでも countObjreactive() を使っていますが、これもただの一例です。

ref()reactive() の違いについては下記が参考になります。

オブジェクトが全部入れ替わるパターンでは……?

ユーザー情報をまるっと入れ替える例です。

ダメな例

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

const user1 = {
  name: '鈴木',
  age: 20,
};

const user2 = {
  name: '佐藤',
  age: 21,
};

let user = reactive(user1);

// 1秒後に「佐藤」に入れ変えたいが、変わらない
setTimeout(() => {
  user = reactive(user2);
}, 1000);
</script>

<template>
  <div>
    名前:{{ user.name }}<br />
    年齢:{{ user.age }}
  </div>
</template>

なぜでしょう?
最初の変数宣言で reactive() をしています。

let user = reactive(user1);

しかしその後別の内容を代入したので reactive() を使っていますが、リンクが切れてしまいました。

setTimeout(() => {
  user = reactive(user2);

動作する例①

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

// ユーザー情報部分は同じなので省略

// user をラッピングした reactive なオブジェクト
const userRactive = reactive({
  user: user1,
});

setTimeout(() => {
  userRactive.user = user2;
}, 1000);
</script>

<template>
  <div>
    名前:{{ userRactive.user.name }}<br />
    年齢:{{ userRactive.user.age }}
  </div>
</template>

これなら表示は更新されます。
userReactive というリアクティブなオブジェクトの user プロパティだけ書き換えてるため、リンクが切れていないからです。

const userRactive = reactive({
  user: user1,
});
setTimeout(() => {
  userRactive.user = user2;

動作する例②

こちらも動作します。
ref() のほうがコードがすっきりします。

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

// ユーザー情報部分は同じなので省略

const user = ref(user1);

setTimeout(() => {
  user.value = user2;
}, 1000);
</script>

<template>
  <div>
    名前:{{ user.name }}<br />
    年齢:{{ user.age }}
  </div>
</template>

オブジェクトを ref() で扱う時はどうするか

ref() でリアクティブにした変数の値を変更するには .value を経由します。
これはオブジェクトも同じです。

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

const user = ref({
  name: '鈴木',
  age: 20,
});

setTimeout(() => {
  user.value.age = 21;
}, 1000);
</script>

<template>
  <div>
    名前:{{ user.name }}<br />
    年齢:{{ user.age }}
  </div>
</template>

まとめ

  • reactive()ref() どちらを使っても同じ結果は得られる
  • 文字列、数値、真偽値などプリミティブな値は ref() を使う
  • オブジェクトのプロパティを変更するだけなら reactive() でもOK
  • オブジェクト内容をまるっと入れ替えるなら ref()

個人的にはオブジェクトも ref() で良いと思いました。

ref() のほうが汎用性が高いのと、.value があることでリアクティブだと判断しやすいことです。

もっと良い方法がありましたら教えていただけると嬉しいです。

11
6
2

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
11
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?