本章では、Vue.jsの「Hello World」アプリケーションを作成し、Vue.jsのリアクティブシステムの基礎について学びます。Composition APIを使用してリアクティブな状態を管理する方法と、Vue.jsのリアクティブシステムがどのように動作するかを理解します。
まずは、シンプルな「Hello World」アプリケーションを作成してみましょう。これにより、Vue.jsの基本的な構造と動作を理解できます。
前章で作成したプロジェクトのsrc/App.vue
ファイルを以下のように変更します:
<script setup>
import { ref } from 'vue'
const message = ref('Hello World!')
</script>
<template>
<div class="app-container">
<h1>{{ message }}</h1>
</div>
</template>
<style scoped>
.app-container {
max-width: 800px;
margin: 0 auto;
padding: 20px;
text-align: center;
}
h1 {
color: #42b983;
}
</style>
このコードは、Composition APIの<script setup>
構文を使用した最小限のVueコンポーネントです。ref
関数を使用してリアクティブな変数message
を作成し、テンプレート内で二重中括弧{{ }}
を使って表示しています。
Vue.jsの最も重要な特徴の一つは、そのリアクティブシステムです。これにより、データの変更を自動的に追跡し、関連するDOMを更新することができます。
Vue 3のリアクティブシステムは、JavaScriptのProxyを使用して実装されています。これにより、オブジェクトのプロパティにアクセスする際や変更する際に特別な処理を実行することができます。
簡単に言うと、Vue.jsはデータの変更を「検知」し、その変更に依存しているコンポーネントを「再レンダリング」します。
Vue 3のComposition APIでは、リアクティブな状態を作成するために主に次の関数を使用します:
ref
:プリミティブ値(文字列、数値など)やオブジェクトをリアクティブにするreactive
:オブジェクトをリアクティブにするcomputed
:リアクティブな値に基づいて計算された値を作成するwatch
/watchEffect
:リアクティブな依存関係の変更を監視する
ref
関数は、任意の値をリアクティブな参照に変換します。プリミティブ値(文字列や数値など)をリアクティブにする場合は、ref
を使用する必要があります。
<script setup>
import { ref } from 'vue'
// プリミティブ値のリアクティブな参照を作成
const count = ref(0)
const name = ref('Vue')
const isActive = ref(true)
// refでオブジェクトをラップすることも可能
const user = ref({ id: 1, name: 'Alice' })
// refの値にアクセスするには .value を使用
console.log(count.value) // 0
count.value++ // 1
// オブジェクトの場合も .value でアクセス
console.log(user.value.name) // Alice
user.value.name = 'Bob'
</script>
<template>
<!-- テンプレート内では .value は不要 -->
<p>Count: {{ count }}</p>
<p>Name: {{ name }}</p>
<p>User: {{ user.name }}</p>
</template>
重要なポイント:
ref
で作成したリアクティブな値には、JavaScriptコード内では.value
を使ってアクセスする必要があります.value
は自動的に展開されるため不要ですref
は、プリミティブ値だけでなくオブジェクトもラップできますが、オブジェクトの場合は内部的にはreactive
を使用しています
reactive
関数は、オブジェクトをリアクティブなプロキシに変換します。ref
と違い、.value
を使わずに直接アクセスできます。
<script setup>
import { reactive } from 'vue'
// リアクティブなオブジェクトを作成
const user = reactive({
id: 1,
name: 'Alice',
address: {
city: 'Tokyo',
country: 'Japan'
}
})
// 直接プロパティにアクセス(.valueは不要)
console.log(user.name) // Alice
user.name = 'Bob'
// ネストされたオブジェクトも自動的にリアクティブになる
console.log(user.address.city) // Tokyo
user.address.city = 'Osaka'
</script>
<template>
<p>User: {{ user.name }}</p>
<p>City: {{ user.address.city }}</p>
</template>
重要なポイント:
reactive
はオブジェクトのみに使用できます(文字列や数値などのプリミティブ値には使用できません).value
は不要)reactive
はProxyを使用しているため、元のオブジェクトへの参照が失われると追跡できなくなる制限がありますどちらを使うべきか迷ったときのガイドライン:
ref
を使用reactive
を使用ref
で管理する方法もあります(Vue公式のスタイルガイドでも推奨されています)リアクティブな状態を使って、ユーザーのインタラクションに応答するアプリケーションを作成してみましょう。
<script setup>
import { ref } from 'vue'
const count = ref(0)
const message = ref('クリックしてカウントを増やしてください')
function increment() {
count.value++
updateMessage()
}
function decrement() {
if (count.value > 0) {
count.value--
updateMessage()
}
}
function updateMessage() {
if (count.value === 0) {
message.value = 'クリックしてカウントを増やしてください'
} else if (count.value === 10) {
message.value = '10回達成!おめでとうございます!'
} else {
message.value = `現在のカウント: ${count.value}`
}
}
</script>
<template>
<div class="counter-app">
<h1>Vue カウンターアプリ</h1>
<p>{{ message }}</p>
<div class="counter-controls">
<button @click="decrement" :disabled="count <= 0">-</button>
<span class="counter-display">{{ count }}</span>
<button @click="increment">+</button>
</div>
</div>
</template>
<style scoped>
.counter-app {
max-width: 500px;
margin: 0 auto;
padding: 20px;
text-align: center;
border: 1px solid #ddd;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.counter-controls {
display: flex;
justify-content: center;
align-items: center;
margin-top: 20px;
}
.counter-display {
font-size: 24px;
width: 50px;
text-align: center;
margin: 0 15px;
}
button {
background-color: #42b983;
color: white;
border: none;
border-radius: 4px;
padding: 8px 16px;
font-size: 18px;
cursor: pointer;
}
button:hover {
background-color: #36a073;
}
button:disabled {
background-color: #ccc;
cursor: not-allowed;
}
</style>
このコードでは、以下のポイントを学べます:
@click
ディレクティブ):disabled="count <= 0"
)
computed
関数を使用すると、リアクティブな依存関係に基づいて計算された値を作成できます。依存する値が変更されるたびに再計算されます。
<script setup>
import { ref, computed } from 'vue'
const firstName = ref('太郎')
const lastName = ref('山田')
// 算出プロパティ
const fullName = computed(() => {
return `${lastName.value} ${firstName.value}`
})
// 入力フィールド用の変数
const price = ref(100)
const quantity = ref(1)
// 算出プロパティで合計金額を計算
const total = computed(() => {
return price.value * quantity.value
})
// 税込み金額(10%の消費税)
const taxIncluded = computed(() => {
return total.value * 1.1
})
</script>
<template>
<div class="computed-demo">
<h2>算出プロパティのデモ</h2>
<div class="example">
<h3>名前の結合</h3>
<div class="form-group">
<label>姓: </label>
<input v-model="lastName" type="text" />
</div>
<div class="form-group">
<label>名: </label>
<input v-model="firstName" type="text" />
</div>
<p>フルネーム: <strong>{{ fullName }}</strong></p>
</div>
<div class="example">
<h3>金額計算</h3>
<div class="form-group">
<label>単価: </label>
<input v-model.number="price" type="number" min="0" /> 円
</div>
<div class="form-group">
<label>数量: </label>
<input v-model.number="quantity" type="number" min="1" />
</div>
<p>合計: <strong>{{ total }}</strong> 円</p>
<p>税込み: <strong>{{ taxIncluded.toFixed(0) }}</strong> 円</p>
</div>
</div>
</template>
<style scoped>
.computed-demo {
max-width: 600px;
margin: 0 auto;
padding: 20px;
}
.example {
border: 1px solid #ddd;
border-radius: 8px;
padding: 20px;
margin-bottom: 20px;
}
.form-group {
margin-bottom: 10px;
}
label {
display: inline-block;
width: 60px;
}
input {
padding: 5px;
border: 1px solid #ddd;
border-radius: 4px;
}
</style>
重要なポイント:
computed
は依存するリアクティブな値が変更されたときのみ再計算されます(キャッシュされます)computed
に移動させるべきですv-model.number
モディファイアは、入力値を数値に変換します
watch
とwatchEffect
を使用すると、リアクティブな依存関係の変更を監視し、副作用を実行できます。
<script setup>
import { ref, watch, watchEffect } from 'vue'
const searchQuery = ref('')
const searchResults = ref([])
const isLoading = ref(false)
// 特定のリアクティブな値を監視
watch(searchQuery, (newValue, oldValue) => {
console.log(`検索クエリが "${oldValue}" から "${newValue}" に変更されました`)
// 値が変更されたときに検索を実行
if (newValue.trim()) {
performSearch(newValue)
} else {
searchResults.value = []
}
}, { immediate: false }) // immediate: true にすると、初期化時にも実行されます
// 検索の実行(擬似的な実装)
async function performSearch(query) {
isLoading.value = true
try {
// APIリクエストを模擬した遅延
await new Promise(resolve => setTimeout(resolve, 1000))
// ダミーの検索結果
searchResults.value = [
{ id: 1, title: `${query} に関する結果 1` },
{ id: 2, title: `${query} についての情報` },
{ id: 3, title: `${query} の詳細` }
]
} catch (error) {
console.error('検索エラー:', error)
searchResults.value = []
} finally {
isLoading.value = false
}
}
// watchEffectは依存関係を自動的に追跡
watchEffect(() => {
// この関数内で使用されるリアクティブな値が変更されると実行される
document.title = searchQuery.value
? `検索中: ${searchQuery.value}`
: 'Vue Search App'
console.log('現在のローディング状態:', isLoading.value)
})
</script>
<template>
<div class="search-app">
<h2>Vue 検索アプリ</h2>
<div class="search-form">
<input
v-model="searchQuery"
type="text"
placeholder="検索ワードを入力..."
@keyup.enter="performSearch(searchQuery)"
/>
<button @click="performSearch(searchQuery)" :disabled="!searchQuery.trim() || isLoading">
検索
</button>
</div>
<div v-if="isLoading" class="loading">
検索中...
</div>
<div v-else-if="searchResults.length" class="results">
<h3>検索結果</h3>
<ul>
<li v-for="result in searchResults" :key="result.id">
{{ result.title }}
</li>
</ul>
</div>
<div v-else-if="searchQuery.trim()" class="no-results">
結果が見つかりませんでした
</div>
</div>
</template>
<style scoped>
.search-app {
max-width: 600px;
margin: 0 auto;
padding: 20px;
}
.search-form {
display: flex;
margin-bottom: 20px;
}
input {
flex: 1;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px 0 0 4px;
font-size: 16px;
}
button {
padding: 8px 16px;
background-color: #42b983;
color: white;
border: none;
border-radius: 0 4px 4px 0;
cursor: pointer;
font-size: 16px;
}
button:disabled {
background-color: #ccc;
}
.loading {
text-align: center;
padding: 20px;
color: #666;
}
.results {
border: 1px solid #ddd;
border-radius: 4px;
padding: 10px 20px;
}
.no-results {
text-align: center;
color: #999;
padding: 20px;
}
</style>
watch
とwatchEffect
の違い:
watch
は特定のリアクティブな値(またはソース)を監視し、その値が変更されたときに指定したコールバック関数を実行しますwatch
は古い値と新しい値の両方にアクセスできますwatchEffect
は、関数内で使用されるすべてのリアクティブな値を自動的に追跡し、それらが変更されたときにその関数を再実行しますwatchEffect
は初期実行時に依存関係を自動的に追跡するため、immediate: true
が必要ありませんVue.jsのリアクティブシステムには、いくつかの制限と注意点があります:
ref
の値にアクセスする際には.value
が必要ですこれらの注意点を詳しく見てみましょう:
<script setup>
import { ref, reactive } from 'vue'
// プロパティの追加と削除
const user = reactive({
name: 'Alice',
age: 25
})
// 既存のオブジェクトに新しいプロパティを追加
user.email = 'alice@example.com' // これは正常に動作します
// 配列の操作
const items = ref(['Apple', 'Banana', 'Cherry'])
// 配列のインデックスによる直接変更
items.value[0] = 'Orange' // これは正常に動作しますが、Vue 2では動作しませんでした
// 推奨される配列の変更方法
items.value.splice(0, 1, 'Orange') // 特に複雑な操作では推奨される方法
// 配列の長さの変更
items.value.length = 2 // これは配列を切り詰めますが、リアクティブに更新されます
// リアクティブオブジェクトの分割代入
const book = reactive({
title: 'Vue.js 3ガイド',
author: {
name: '山田太郎'
}
})
// 分割代入するとリアクティブ性が失われる
const { title } = book
// titleはもはやリアクティブではありません
// toRefs関数を使用した分割代入(この場合はVueからのインポートが必要)
// import { toRefs } from 'vue'
// const { title } = toRefs(book)
// これでtitleはリアクティブなref型として保持されます
</script>
Vue.jsのリアクティブシステムを効果的に使用するためのベストプラクティスを紹介します:
watch
またはwatchEffect
を使用しますコンポーサブル関数(Composables)は、Vue 3のComposition APIを使用して、再利用可能なロジックをカプセル化する方法です。
簡単なカウンターコンポーサブル関数の例:
// src/composables/useCounter.js
import { ref } from 'vue'
export function useCounter(initialValue = 0) {
const count = ref(initialValue)
function increment() {
count.value++
}
function decrement() {
count.value--
}
function reset() {
count.value = initialValue
}
return {
count,
increment,
decrement,
reset
}
}
このコンポーサブル関数の使用例:
<script setup>
import { useCounter } from './composables/useCounter'
// カウンターコンポーサブルを使用
const { count, increment, decrement, reset } = useCounter(10)
</script>
<template>
<div>
<p>Count: {{ count }}</p>
<button @click="increment">+</button>
<button @click="decrement">-</button>
<button @click="reset">リセット</button>
</div>
</template>
もう少し実用的な例として、ローカルストレージと連携するコンポーザブル関数を見てみましょう:
// src/composables/useLocalStorage.js
import { ref, watch } from 'vue'
export function useLocalStorage(key, defaultValue) {
// ローカルストレージから値を取得するか、デフォルト値を使用
const storedValue = localStorage.getItem(key)
const value = ref(storedValue ? JSON.parse(storedValue) : defaultValue)
// 値が変更されたらローカルストレージに保存
watch(value, newValue => {
localStorage.setItem(key, JSON.stringify(newValue))
})
return value
}
使用例:
<script setup>
import { useLocalStorage } from './composables/useLocalStorage'
// テーマ設定をローカルストレージに保存
const theme = useLocalStorage('theme', 'light')
function toggleTheme() {
theme.value = theme.value === 'light' ? 'dark' : 'light'
}
</script>
<template>
<div :class="theme">
<p>現在のテーマ: {{ theme }}</p>
<button @click="toggleTheme">テーマ切替</button>
</div>
</template>
<style scoped>
.light {
background-color: #fff;
color: #333;
}
.dark {
background-color: #333;
color: #fff;
}
</style>
コンポーザブル関数は、複数のコンポーネント間でロジックを再利用する強力な方法です。APIリクエスト、フォームのバリデーション、ユーザー認証など、様々なユースケースに適用できます。
本章では、Vue.jsの「Hello World」アプリケーションを作成し、リアクティブシステムの基礎について学びました。Composition APIを使用したリアクティブな状態の管理方法、ref
とreactive
の違い、算出プロパティの使用方法、ウォッチャーの使用方法、そしてリアクティブシステムの制限と注意点について理解しました。
また、コンポーザブル関数を使用して再利用可能なロジックをカプセル化する方法も紹介しました。これらの知識は、Vue.jsを使用した効果的なアプリケーション開発の基盤となります。
次の章では、データバインディングと条件分岐について詳しく学びます。