404 Not Found

404 Not Found


nginx

Vue 3 のリアクティブデータ:ref と reactive の完全ガイド

Vue 3のリアクティブシステムは、その心臓部です。変数を変更すると、ページ上でその変数が使用されているすべての箇所が自動的に更新されます。だからこそ、VueはjQueryに比べて10倍も簡単にコーディングできるのです。このリアクティブシステムは、ref()reactive()という2つのコアAPIによって支えられています。

ref は あらゆる種類のデータ(数値、文字列、オブジェクト)をラップするために使用されますが、reactive は オブジェクトのみをラップできます。この 2 つは使い方が異なり、このレッスンではその違いを完全に理解できるようになります。

1. 学習内容


2. うまくいかなかったカウンターページの物語

(1) 課題:カウントが変更されても、UIが更新されない

アリスは、ECサイトの管理画面向けに、初めてのVue 3コンポーネントを作成しました。彼女は「商品の在庫カウンター」を実装しようと試みました:

JS
// ❌ The "Flip" Version
let count = 0

function increment() {
  count++
  console.log(count)  // 1, 2, 3... It looks like
}
HTML
<template>
  <p>Stock: {{ count }}</p>
  <button @click="increment">+1</button>
</template>

彼女はボタンを5回クリックした。コンソールには1、2、3、4、5と表示された。しかし、ページには依然として「在庫:0」と表示されたままである。

プロダクトマネージャーのチャーリーが、彼女の肩越しに覗き込む:

「アリス、カウントが更新されないんだけど? 5回クリックしたのに。Vueが壊れてるのかな?」

(2) Vueのリアクティブソリューション:ref() / reactive()

VUE
<!-- ✅ Correct Version -->
<template>
  <p>Stock: {{ count }}</p>
  <button @click="increment">+1</button>
</template>

<script setup>
import { ref } from 'vue'

// Use ref() Wrapper → Vue Knows it should be \"Reactive\"
const count = ref(0)

function increment() {
  // In JS Key Points .value
  count.value++
  // Automatic unpacking in templates,Not necessary .value
}
</script>

このボタンをクリックすると、ページが即座に更新されます。変数が変更されると、DOMが自動的に更新されます。

(3) 収益

Vue 3 のリアクティブ性を学んだ後:


3. リアクティビティの原則:プロキシと依存関係の収集

(1) 3段階の対応プロセス

100%
sequenceDiagram
    participant T as Template
    participant C as Component
    participant R as Reactive<br/>(Proxy)
    participant D as Data
    
    T->>C: 1. Render (Read count)
    C->>R: 2. get count
    R->>D: 3. Read 0
    R->>R: 4. Track: Who's using it? count?
    R-->>C: 5. Return 0
    C-->>T: 6. Display "Stock: 0"
    
    Note over D,T: The user clicks the button
    
    D->>R: 7. set count = 1
    R->>R: 8. Trigger: Notice to All Users count the place
    R->>C: 9. Re-render
    C-->>T: 10. Display "Stock: 1"

(2) 要点

(3) Vue 2 対 Vue 3:リアクティブ性の比較

寸法 図面 2 図面 3
基盤となるAPI Object.defineProperty ES6 Proxy
配列ウォッチャー 特別な処理が必要 ネイティブサポート
マップ/セットのウォッチ 未対応 対応
新規物件を追加 Vue.set() が必要 自動返信
パフォーマンス 平均 向上(レイジープロキシ)

4. ref(): 任意の値をラップする

(1) 基本的な使い方

JS
import { ref } from 'vue'

// Packaging Numbers
const count = ref(0)

// Packaging String
const name = ref('Alice')

// Boolean Packaging
const isVip = ref(false)

// Packaging null
const data = ref(null)

// Packaging Array
const items = ref([1, 2, 3])

// Items to Be Packaged
const user = ref({ name: 'Bob', age: 25 })

(2) 値へのアクセス:JavaScriptでは .value を使用してください。テンプレートが自動的に展開します。

VUE
<template>
  <!-- Using variable names directly in templates(Automatic Unpacking) -->
  <p>{{ count }}</p>          <!-- 0 -->
  <p>{{ name }}</p>          <!-- Alice -->
  <p>{{ user.name }}</p>     <!-- Bob -->
</template>

<script setup>
import { ref } from 'vue'
const count = ref(0)
const name = ref('Alice')
const user = ref({ name: 'Bob', age: 25 })

function logCount() {
  // In JS Must use .value
  console.log(count.value)  // 0
  console.log(name.value)   // Alice
  console.log(user.value.name)  // Bob
}
</script>

(3) refの「双方向性」

JS
import { ref } from 'vue'

const count = ref(0)

// Read
console.log(count.value)  // 0

// Edit
count.value = 1
console.log(count.value)  // 1

// Modify Nested Properties(ref When wrapping an object)
const user = ref({ name: 'Bob' })
user.value.name = 'Alice'  // ✅ Responsive
user.value = { name: 'Charlie' }  // ✅ The entire replacement also responds

5. reactive(): ラッパーオブジェクト(ディープリアクティビティ)

(1) 基本的な使い方

JS
import { reactive } from 'vue'

// Only objects can be passed
const state = reactive({
  count: 0,
  user: { name: 'Alice', age: 25 },
  items: [1, 2, 3]
})

// No access required .value
console.log(state.count)  // 0
console.log(state.user.name)  // Alice

// Edit Auto-Reply
state.count = 1
state.user.name = 'Bob'
state.items.push(4)

(2) 詳細な回答

JS
const state = reactive({
  level1: {
    level2: {
      level3: {
        value: 'deep'
      }
    }
  }
})

// Changes made at any level are automatically reflected
state.level1.level2.level3.value = 'updated'  // ✅ Trigger an update

(3) リアクティブプログラミングの限界

JS
// ❌ You cannot directly replace the entire object.(No longer responsive)
let state = reactive({ count: 0 })
state = reactive({ count: 1 })  // Wrong! state variable itself changed, but template uses old reference.

// ❌ Deconstruction results in the loss of responsiveness
const state = reactive({ count: 0, name: 'Alice' })
const { count, name } = state  // count/name Convert to a regular variable,Not responding

// ✅ Destructuring Reactivity: use toRefs
import { toRefs } from 'vue'
const { count, name } = toRefs(state)  // count/name.value Stay Responsive

6. ref と reactive の5つの主な違い

側面 ref 反応性
ラップ可能な型 任意の値(数値、文字列、オブジェクト、配列) オブジェクトのみ(Object/Array)
アクセス方法 .value (JS) 直接アクセス (JS)
テンプレートのアクセス 自動展開(.value なし) 直接アクセス
すべて置換 ref.value = {...} ✅ 反応あり reactive({...}) = ... ❌ 反応なし
分解 応答は保持される (toRefs) 応答は失われる (toRefs が必要)

(4) 選定に関する推奨事項

シナリオ 推奨事項
プリミティブ型(数値/文字列/ブール値) ref()
単一のレスポンシブオブジェクト ref({}) または reactive({}) のいずれか
複数の関連するフィールドで構成される状態 reactive({})(Vuexのようなもの)
完全に交換が必要 ref({}) (より柔軟性が高い)
プロップの受け渡し/発光 ref() (境界を明確にする)

7. 浅いリアクティブ性:shallowRef / shallowReactive

(1) 表面的な回答はどのような場合に用いるべきか?

大きなオブジェクトがあるものの、トップレベルのフィールドの変更のみを気にする場合は、浅いレスポンスを使用することでパフォーマンスを向上させることができます

JS
import { shallowRef, shallowReactive, triggerRef } from 'vue'

// shallowRef:Follow Only .value Replace All,Do not track internal properties
const bigData = shallowRef({ items: [...50000 items] })

bigData.value.items.push('new')  // ❌ Do not trigger an update
bigData.value = { items: [newItems] }  // ✅ Trigger an update(Replace All)

// shallowReactive:Track only top-level properties,No in-depth response
const state = shallowReactive({
  user: { name: 'Alice' },
  settings: { theme: 'dark' }
})

state.user.name = 'Bob'  // ❌ Does not trigger
state.user = { name: 'Bob' }  // ✅ Trigger(Top-Level Property Replacement)

(2) 更新を強制する

JS
import { triggerRef } from 'vue'

const bigData = shallowRef({ items: [...] })
bigData.value.items.push('new')  // Do not respond by default
triggerRef(bigData)  // Manually Trigger an Update

代表的なシナリオ:大規模なテーブル(10万行)、大規模なリスト、および内部プロパティが頻繁に変更されるものの、重要なのは全体的な状態のみであるような状況。


8. toRef / toRefs:リアクティブ性を維持しつつデコンストラクションを行う

(1) toRefs: リアクティブオブジェクトのデストラクチャリング

JS
import { reactive, toRefs } from 'vue'

const state = reactive({
  count: 0,
  name: 'Alice'
})

// ❌ General Deconstruction:Missing Response
const { count, name } = state

// ✅ toRefs Deconstruction:Stay Responsive
const { count, name } = toRefs(state)

// Used in templates
// <p>{{ count }}</p>  <!-- Still responding -->

(2) toRef:通常の値をrefに変換する

JS
import { toRef, ref } from 'vue'

// Scene: Select one field from props
const props = defineProps({ count: Number })

// ❌ Directly Deconstructing Missing Responses
const { count } = props  // count It's ordinary number

// ✅ toRef Packaging
const count = toRef(props, 'count')  // count is ref(props.count)

// Usage
count.value++  // Responsive Changes props

9. レスポンシブデザインの5つの落とし穴

(1) 落とし穴 1:反応性オブジェクトを直接置き換えること

JS
let state = reactive({ count: 0 })
// ❌ state The entire variable has been changed,But the template uses the old version state
state = reactive({ count: 1 })
// ✅ Edit Properties
state.count = 1

(2) 落とし穴その2:反応的態度を分解し、反応そのものを失うこと

JS
const state = reactive({ count: 0 })
// ❌ count It is a regular variable
const { count } = state
// ✅ Use toRefs
const { count } = toRefs(state)

(3) 落とし穴 3:配列への直接代入

JS
const items = ref([1, 2, 3])
// ❌ Directly push won't react (need triggerRef)
items.value.push(4)
// ✅ Replace All
items.value = [...items.value, 4]

(4) 落とし穴 4:ref でラップされたオブジェクトの内容を直接変更すること

JS
// It generally does not expire.,However, there are edge cases
const user = ref({ name: 'Alice' })
user.value.name = 'Bob'  // ✅ Response(ref Wrapped objects are automatically unwrapped)

(5) 落とし穴 5:ref 型と reactive 型の混同

JS
// ❌ Type error:reactive Cannot wrap the original value
const count = reactive(0)  // Error: value cannot be made reactive
// ✅ Use ref
const count = ref(0)

10. 完全な例:レスポンシブ対応のショッピングカート

(1) ▶ サンプル:. ref の基本的な使い方

VUE
<template>
  <div>
    <p>Count: {{ count }}</p>
    <button @click="increment">+1</button>
    <button @click="reset">Reset</button>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const count = ref(0)

function increment() {
  count.value++  // JS Must be used in .value
}

function reset() {
  count.value = 0
}
</script>
▶ 試してみよう

(2) ▶ サンプル:. リアクティブ・ラッパーオブジェクト

VUE
<template>
  <div>
    <p>User: {{ state.user.name }}</p>
    <p>Age: {{ state.user.age }}</p>
    <button @click="birthday">+1 Year</button>
  </div>
</template>

<script setup>
import { reactive } from 'vue'

const state = reactive({
  user: { name: 'Alice', age: 25 }
})

function birthday() {
  state.user.age++  // Edit directly,Automatic Response
}
</script>
▶ 試してみよう

(3) ▶ サンプル:. ref と reactive の比較

JS
import { ref, reactive } from 'vue'

// ref Style
const userRef = ref({ name: 'Alice', age: 25 })
// Edit:
userRef.value.name = 'Bob'  // ✅ Response
userRef.value = { name: 'Charlie', age: 30 }  // ✅ Response

// reactive Style
const userReactive = reactive({ name: 'Alice', age: 25 })
// Edit:
userReactive.name = 'Bob'  // ✅ Response
// userReactive = reactive({...})  // ❌ Failure
▶ 試してみよう

(4) ▶ サンプル:. toRefs—レスポンスを保持したままのデストラクチャリング

VUE
<template>
  <div>
    <p>{{ count }} - {{ name }}</p>
    <button @click="increment">+1</button>
  </div>
</template>

<script setup>
import { reactive, toRefs } from 'vue'

const state = reactive({ count: 0, name: 'Alice' })
const { count, name } = toRefs(state)  // Stay Responsive

function increment() {
  count.value++  // count is ref, So use .value
}
</script>
▶ 試してみよう

(5) ▶ サンプル:. 浅いレスポンス + triggerRef

JS
import { shallowRef, triggerRef } from 'vue'

// Large Table 100,000 rows → use shallowRef
const bigTable = shallowRef({
  rows: Array.from({ length: 100000 }, (_, i) => ({ id: i, name: `Row ${i}` }))
})

// Replace All(Highly efficient)
function reloadData(newRows) {
  bigTable.value = { rows: newRows }
}

// Modify Internal Properties(Not responding,Must be done manually trigger)
function updateRow(id, newName) {
  const row = bigTable.value.rows.find(r => r.id === id)
  if (row) {
    row.name = newName
    triggerRef(bigTable)  // Manually Trigger an Update
  }
}
▶ 試してみよう

(6) ▶ サンプル:. よくある5つの間違いに関するクイックリファレンス

エラー 症状 解決策
let state = reactive({...}); state = {...} 状態全体を置き換える;テンプレートは更新されない プロパティ state.x = 1 を変更
const { count } = state 分解 count を通常の変数に変換 toRefs(state) を使用
reactive(0) 元のパッケージの値 エラー 元の値には ref() を使用してください
count.value++ がテンプレート内にある エラー テンプレート内に直接 {{ count }} がある
array[index] = x 直接割り当て 応答しない場合がある splice を使用するか、セクション全体を置き換えてください

❓ よくある質問

Q refreactive、どちらを選べばいいですか?
A 公式の推奨は、ref を独占的に使用することです(理由:ref はあらゆる型に対応しており、プロパティや関数の置換・受け渡し全体がより明確になるため)。reactiveは、「状態が複数の関連するフィールドで構成される」シナリオ(Vuexなど)に適しています。初心者はまずrefを学び、習熟してからreactiveを使用することをお勧めします。
Q なぜ ref には .value が必要なのに、テンプレートには必要ないのですか?
A .value は、JavaScript エンジンに対して「これは ref でラップされた値であり、リアクティブにする必要がある」と明示的に伝えるために使用されます。Vue はテンプレートを独自に解析するため、どの要素が ref であるかを認識し、自動的にラップを解除します。要するに、JavaScript では(曖昧さを避けるために).value が必要ですが、テンプレートでは(利便性のために)ref が自動的にアンラップされるのです。
Q ref を使ってオブジェクトをラップする場合、そのプロパティにアクセスするには .value.x を使用する必要がありますか?
A いいえ。ref({name: 'Alice'}) の後、テンプレートでは {{ user.name }} が直接使用され、JavaScript では user.value.name が使用されます。Vue は、ref でラップされたオブジェクトの内部プロパティへのアクセスを自動的に処理します。
Q リアクティブなデストラクチャリングを行うと、リアクティブ性が失われてしまいますか?これを解決するにはどうすればよいですか?
A toRefs() で囲んでください。const { count, name } = toRefs(state) の後、countname は ref になりますが、テンプレート内の {{ count }} と JavaScript 内の count.value はどちらもリアクティブ性を維持します。
Q shallowRefとrefの違いは何ですか?
A refは深参照(内部プロパティの変更も反映されます)であるのに対し、shallowRef.value全体の置換のみを追跡します(内部の変更は追跡しません)。データセットが巨大(10万件以上)で、全体的な置換のみを気にする場合は、shallowRef を使用する方がパフォーマンスが向上します。
Q リアクティブ挙動が機能しなくなる一般的な原因は何ですか?
A 最も一般的な5つの原因は次のとおりです:(1) リアクティブオブジェクトを直接置き換えること;(2) toRefs を使用せずにリアクティブをデストラクチャリングすること; (3) プリミティブ値を、リアクティブでない ref でラップすること(代わりに reactive を使用するか、ref を保持してください); (4) setupの外でrefを作成すること(これによりリアクティブ性が失われる);(5) 配列への直接代入(代わりにspliceを使用してください)。
Q reactiveMapSet をラップできますか?
A はい。Proxy を使用して実装されている Vue 3 の reactive は、MapSetWeakMap、および WeakSet をサポートしています。Vue 2 ではこれはサポートされていません。

📖 まとめ


📝 練習問題

(1) 基礎問題(難易度:⭐)

ref を使用して、簡単なショッピングカートの数量カウンターを作成してください:

(2) 上級問題(難易度:⭐⭐)

Reactive を使用して、ユーザープロフィールの編集フォームを作成してください:

(3) チャレンジ問題(難易度:⭐⭐⭐)

完全なToDoアプリの実装(データ層):

  1. todos 配列を reactive で包む
  2. 各ToDoには { id, text, done } が含まれています
  3. 提供されているメソッド:addTodo(text) / toggleTodo(id) / removeTodo(id) / clearDone()
  4. 以下の計算プロパティを提供してください:activeCount(未完了項目の数)および doneCount(完了項目の数)
  5. テンプレートに、ToDoリストの全項目と件数を表示する

初期データ:{ id: 1, text: 'Learn Vue 3', done: false }

Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%