# リアクティブの基礎

# リアクティブな状態の宣言

JavaScript のオブジェクトからリアクティブな状態を作る目的で、reactive メソッドを用いることができます:

import { reactive } from 'vue'

// リアクティブな状態
const state = reactive({
  count: 0
})
1
2
3
4
5
6

reactive は Vue 2.x における Vue.observable() API に相当し、RxJS における observables との混同を避けるために改名されました。ここで、返される状態はリアクティブオブジェクトです。リアクティブの変換は "deep" であり、渡されたオブジェクトのすべての入れ子になっているプロパティに影響を与えます。

Vue におけるリアクティブな状態の重要なユースケースは描画の際に用いることができることです。依存関係の追跡のおかげで、リアクティブな状態が変化するとビューが自動的に更新されます。

これがまさに Vue のリアクティブシステムの本質です。コンポーネント内の data() でオブジェクトを返す際に、内部的には reactive() によってリアクティブを実現しています。テンプレートはこれらのリアクティブなプロパティを利用する Render 関数にコンパイルされます。

reactive についての詳細は 基本リアクティビティ API セクションを参照してください

# 独立したリアクティブな値を ref として作成する

独立したプリミティブ値(例えば文字列)があって、それをリアクティブにしたい場合を想像してみてください。もちろん、同じ値の文字列を単一のプロパティとして持つオブジェクトを作成して reactive に渡すこともできます。Vue にはこれと同じことをしてくれる ref メソッドがあります:

import { ref } from 'vue'

const count = ref(0)
1
2
3

ref は、オブジェクト内部の値をリアクティブな参照(refrence)として機能させるミュータブルなオブジェクトを返します(これが名前の由来です)。このオブジェクトには value という名前のプロパティが 1 つだけ含まれています:

import { ref } from 'vue'

const count = ref(0)
console.log(count.value) // 0

count.value++
console.log(count.value) // 1
1
2
3
4
5
6
7

# ref のアンラップ

ref がレンダーコンテキスト(render contenxt - setup() によって返されるオブジェクト)のプロパティとして返されていてテンプレート内でアクセスされる場合、自動的に内部の値に浅くアンラップ(ref でラップされた値を取り出す)されます。入れ子になった ref だけが、テンプレート内で .value が必要です:

<template>
  <div>
    <span>{{ count }}</span>
    <button @click="count ++">Increment count</button>
    <button @click="nested.count.value ++">Nested Increment count</button>
  </div>
</template>

<script>
  import { ref } from 'vue'
  export default {
    setup() {
      const count = ref(0)
      return {
        count,

        nested: {
          count
        }
      }
    }
  }
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

TIP

実際のオブジェクトインスタンスにアクセスしたくない場合は、reactive でラップできます:

nested: reactive({
  count
})
1
2
3

# リアクティブオブジェクト内でのアクセス

ref がリアクティブオブジェクトのプロパティとしてアクセスまたは更新される際に、自動的に内部の値にアンラップされて通常のプロパティのように振る舞います:

const count = ref(0)
const state = reactive({
  count
})

console.log(state.count) // 0

state.count = 1
console.log(count.value) // 1
1
2
3
4
5
6
7
8
9

既に ref とリンクしているプロパティに新しい ref が割り当てられた場合、古い ref に取って代わることになります:

const otherCount = ref(2)

state.count = otherCount
console.log(state.count) // 2
console.log(count.value) // 1
1
2
3
4
5

ref のアンラップはリアクティブな Object の中の入れ子となっている場合にのみ発生します。ref が ArrayMap (opens new window) のようなネイティブのコレクション型からアクセスされた場合、アンラップは行われません:

const books = reactive([ref('Vue 3 Guide')])
// ここでは .value が必要です
console.log(books[0].value)

const map = reactive(new Map([['count', ref(0)]]))
// ここでは .value が必要です
console.log(map.get('count').value)
1
2
3
4
5
6
7

# リアクティブな状態の分割代入

大きなリアクティブオブジェクトのプロパティをいくつかを使いたいときに、ES6 の分割代入 (opens new window)を使ってプロパティを取得したくなることがあります:

import { reactive } from 'vue'

const book = reactive({
  author: 'Vue Team',
  year: '2020',
  title: 'Vue 3 Guide',
  description: 'You are reading this book right now ;)',
  price: 'free'
})

let { author, title } = book
1
2
3
4
5
6
7
8
9
10
11

残念ながら、このような分割代入をすることで両方のプロパティのリアクティブが失われてしまいます。このような場合においてはリアクティブオブジェクトを ref のセットに変換する必要があります。これらの ref は元となるオブジェクトへのリアクティブな接続を保持します:

import { reactive, toRefs } from 'vue'

const book = reactive({
  author: 'Vue Team',
  year: '2020',
  title: 'Vue 3 Guide',
  description: 'You are reading this book right now ;)',
  price: 'free'
})

let { author, title } = toRefs(book)

title.value = 'Vue 3 Detailed Guide' // ここで title は ref であるため .value を用いる必要があります
console.log(book.title) // 'Vue 3 Detailed Guide'
1
2
3
4
5
6
7
8
9
10
11
12
13
14

ref で作成した 参照 についての詳細は 参照 (refs) API セクションを参照してください

# readonly でリアクティブオブジェクトの変更を防ぐ

リアクティブオブジェクト(refreactive)の変更を追跡しながらも、アプリケーションのある場所からの変更は防ぎたい場合があります。例えば、Provide されたリアクティブオブジェクトがある場合、それが注入された場所からの変更は防ぎたいことがあります。そうするために、元のオブジェクトに対する読み取り専用のプロキシを作成します:

import { reactive, readonly } from 'vue'

const original = reactive({ count: 0 })

const copy = readonly(original)

// original を変更すると copy 側の依存ウォッチャが発動します
original.count++

// copy を変更しようとすると失敗し、警告が表示されます
copy.count++ // warning: "Set operation on key 'count' failed: target is readonly."
1
2
3
4
5
6
7
8
9
10
11