リアクティブデータ

リアクティブはVue3の最も中核となる概念です。

この章では、リアクティブとは何かを理解し、以下の使い方を学びます:refとreactiveデータを管理し、カテゴリフィルター機能を実装します。


リアクティブとは?

リアクティブとは、次のことを意味します:データが変化したとき、ページが自動的に更新されます。

従来のJSの書き方は次のとおりです:変数を変更したら、手動でDOMを更新する必要があります。

一方、Vue3のリアクティブシステムはデータの変化を自動的に追跡し、ビューの更新を通知します。データだけに集中すればよく、DOMを気にする必要はありません。

例

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

// ref でラップされた変数はリアクティブです
const count = ref(0)

// count を変更すると、テンプレート内の {{ count }} は自動的に更新されます
function increment() {
count.value++ // 注意:JS内では .value でアクセスする必要があります
}
</script>

<template>
  <p>クリック回数:{{ count }}</p>
  <!-- テンプレート内では自動的に展開され、.value は不要です -->
  <button @click="increment">+1</button>
</template>

ボタンをクリックすると、ページに表示される数字が自動的に変化します。これがリアクティブです。

あなたは何も書く必要はありません、document.querySelector 或 innerHTMLページを更新するために。


ref — 基本型を扱う

refこれはVue3で最もよく使われるリアクティブAPIで、基本型のデータをラップするのに適しています。

基本型には、文字列、数値、真偽値、null、undefined が含まれます。

構文

例

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

// ref を作成
const name = ref('サンプル') // 文字列
const age = ref(18) // 数値
const isVip = ref(false) // 真偽値

// .value を読み取る
console.log(name.value)         // "サンプル"
console.log(age.value)          // 18

// .value を変更する
name.value = 'Hello サンプル'
age.value++
isVip.value = !isVip.value
</script>

<template>
  <p>名前:{{ name }}</p>
  <!-- テンプレート内では、ref は自動的に展開され、.value は不要です -->
  <p>年齢:{{ age }}</p>
  <p>VIP:{{ isVip }}</p>
</template>

最も重要な覚え方:JSコードでは .value を付け、テンプレートでは付ける必要はありません。これは初心者が最もよく犯す間違いです。


reactive — オブジェクトと配列を扱う

reactiveオブジェクトや配列などの参照型データをラップするのに適しています。

ref と異なり、reactive では必要ありません.value、直接プロパティにアクセスできます。

例

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

// reactive でオブジェクトをラップする
const user = reactive({
name: '小明',
  age: 20,
hobbies: ['编程', '跑步']
})

// 直接アクセスして変更でき、.value は不要です
console.log(user.name) // "小明"
user.name = '小华' // リアクティブ更新
user.hobbies.push('阅读') // 配列操作もリアクティブです
</script>

<template>
  <p>名前:{{ user.name }}</p>
  <p>年齢:{{ user.age }}</p>
  <ul>
    <li v-for="(hobby, index) in user.hobbies" :key="index">
      {{ hobby }}
    </li>
  </ul>
</template>

reactive の2つの制限

制限説明解決策
オブジェクト型のみラップできる文字列や数値などの基本型は渡せない基本型は ref を使う
全体を置き換えられないオブジェクト全体を新しいオブジェクトに代入できないObject.assign() を使うか、ref に変更する

例

import { reactive, ref } from 'vue'

// エラー:reactive オブジェクトを全体として置き換えることはできません
const state = reactive({ count: 0 })
// state = { count: 1 } // これではダメで、リアクティブ性が失われます

// 正しい方法1:全体を置き換えるのではなく、プロパティを変更する
state.count = 1

// 正しい方法2:ref でオブジェクトをラップする(ref は全体の置き換えが可能)
const stateRef = ref({ count: 0 })
stateRef.value = { count: 1 }  // 可能!ref の .value は置き換えられるため

ref vs reactive の使い分けのタイミング

シーン推奨例
単一の文字列/数値/真偽値refconst name = ref('小明')
単一の配列refconst list = ref([])
単一のオブジェクトref または reactive どちらでもよいconst user = ref({})
互いに関係のない複数の状態それぞれ ref を使う1行に1つの ref
強く関連する一連の状態reactive例:フォームデータ、設定項目の集合

実際の開発で最も一般的な方法は次のとおりです:すべて ref を使う(オブジェクトや配列も含む)。

このようにすると書き方が統一され、ref と reactive の間で考え方を切り替える必要がありません。

本チュートリアルの以降の章では、すべての状態を管理するために ref を統一して使用します。必要になる場合は.valueその箇所を特別に注記します。


computed — 算出プロパティ

computedは「既存のデータから計算されるデータ」です。

これには2つの中核的な特性があります:キャッシュ——依存が変わらない場合は再計算されません;自動追跡——依存が変化すると自動的に更新されます。

例

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

const articles = ref([
{ id: 1, title: 'Vue3 入门', category: 'Vue' },
{ id: 2, title: 'Promise 详解', category: 'JavaScript' },
{ id: 3, title: 'Grid 布局', category: 'CSS' },
  { id: 4, title: 'Composition API', category: 'Vue' }
])

const activeCategory = ref('全部')

// computed は activeCategory に基づいて記事をフィルタリングします
const filteredArticles = computed(() => {
if (activeCategory.value === '全部') {
    return articles.value
  }
  return articles.value.filter(a => a.category === activeCategory.value)
})

// computed は集計にも使えます
const totalCount = computed(() => articles.value.length)
const filteredCount = computed(() => filteredArticles.value.length)
</script>

<template>
  <p>合計 {{ totalCount }} 記事、現在 {{ filteredCount }} 件表示中</p>

  <!-- カテゴリボタン -->
  <button @click="activeCategory = '全部'">すべて</button>
  <button @click="activeCategory = 'Vue'">Vue</button>
  <button @click="activeCategory = 'JavaScript'">JavaScript</button>
  <button @click="activeCategory = 'CSS'">CSS</button>

  <!-- フィルタリング後の記事リスト -->
  <div v-for="article in filteredArticles" :key="article.id">
    <h3>{{ article.title }}</h3>
    <span>{{ article.category }}</span>
  </div>
</template>

~たびにactiveCategory変化すると、filteredArticles自動的に再計算され、ページが自動的に更新されます。

注意:computed が返すのは ref なので、JS内でアクセスする場合にも必要です.value。ただし、上の例ではテンプレート内で直接使用しており、テンプレート内では自動的に展開されます。


やってみよう:ブログにカテゴリフィルターを追加する

computed をブログのホームページに適用して、カテゴリボタンをクリックして記事をフィルタリングする機能を実装します。

例

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

const articles = ref([
{ id: 1, title: 'Vue3 入門完全ガイド', summary: 'ゼロからVue3を学ぶ', category: 'Vue', date: '2024-05-10' }
{ id: 2, title: 'JS 非同期プログラミング詳解', summary: 'Promise と async/await を理解する', category: 'JavaScript', date: '2024-05-08' },
{ id: 3, title: 'CSS Grid 布局实战', summary: '用 Grid 实现响应式布局', category: 'CSS', date: '2024-05-05' },
{ id: 4, title: 'Vue3 リアクティブ原理', summary: 'ref と reactive を深く理解する', category: 'Vue', date: '2024-05-03' },
{ id: 5, title: 'Flexbox 完全ガイド', summary: '1記事で学ぶフレキシブルレイアウト', category: 'CSS', date: '2024-05-01' },
])

// すべてのカテゴリ(重複除去)
const categories = computed(() => {
  const cats = articles.value.map(a => a.category)
return ['すべて', ...new Set(cats)] // 先頭に「すべて」を追加する
})

// 現在選択中のカテゴリ
const activeCategory = ref('すべて')

// カテゴリに基づいて記事をフィルタリングする
const filteredArticles = computed(() => {
if (activeCategory.value === 'すべて') {
    return articles.value
  }
  return articles.value.filter(a => a.category === activeCategory.value)
})
</script>

<template>
  <div class="home">
    <h2>最新記事</h2>

    <!-- カテゴリ絞り込みボタン -->
    <div class="category-bar">
      <button
       v-for="cat in categories"
       :key="cat"
       :class="{ active: activeCategory === cat }"
       @click="activeCategory = cat"
     >
        {{ cat }}
      </button>
    </div>

    <!-- フィルタリング結果の件数 -->
    <p class="result-info">全 {{ filteredArticles.length }} 件</p>

    <!-- 記事リスト -->
    <div class="article-grid">
      <div v-for="article in filteredArticles" :key="article.id" class="card">
        <span class="tag">{{ article.category }}</span>
        <h3>{{ article.title }}</h3>
        <p>{{ article.summary }}</p>
        <span class="date">{{ article.date }}</span>
      </div>
    </div>
  </div>
</template>

<style scoped>
.category-bar {
  display: flex;
  gap: 10px;
  margin-bottom: 20px;
  flex-wrap: wrap;
}

.category-bar button {
  padding: 6px 16px;
  border: 1px solid #ddd;
  border-radius: 20px;
  background: #fff;
  cursor: pointer;
  font-size: 14px;
  transition: all 0.2s;
}

.category-bar button.active {
  background: #427DB8;
  color: #fff;
  border-color: #427DB8;
}

.category-bar button:hover {
  border-color: #427DB8;
}

.result-info {
  color: #999;
  font-size: 14px;
  margin-bottom: 16px;
}
</style>

さまざまなカテゴリボタンをクリックすると、記事リストがリアルタイムでフィルタリングされます。DOM操作のコードを書く必要はなく、データ変更のロジックに集中するだけでよいのです。


本章のまとめ

この章では、Vue3 リアクティブシステムの3つのコアAPIを習得しました:ref は基本型をラップし(JSでは .value を使用)、reactive はオブジェクトをラップし、computed は計算データを導出します。

computed + カテゴリ絞り込みボタンによって、「データ駆動型ビュー」の開発パターンを体験しました。データを変更すると、ページが自動的に更新されます。

その他の拡張