Vue

初級 | 8分 で読める | 2026.04.24

公式ドキュメント

この記事の要点

ref()reactive()でリアクティブな状態管理
<script setup>で簡潔に Composition API を記述
computed()で算出プロパティ、watch()で副作用を実装

リアクティビティ基礎

API説明
ref(value)プリミティブ値をリアクティブ化
reactive(obj)オブジェクトをリアクティブ化
computed(() => ...)算出プロパティ
watch(source, callback)監視
watchEffect(() => ...)自動依存追跡の監視
<script setup>
import { ref, reactive, computed } from 'vue'

const count = ref(0)
const user = reactive({
  name: 'Alice',
  age: 25
})

const doubled = computed(() => count.value * 2)

function increment() {
  count.value++
}
</script>

<template>
  <p>{{ count }} - {{ doubled }}</p>
  <button @click="increment">+1</button>
  <p>{{ user.name }} ({{ user.age }})</p>
</template>

ポイント: ref.valueでアクセス、reactiveはそのままアクセスします。テンプレート内では.value自動でアンラップされます。

テンプレート構文

構文説明
{{ message }}テキスト補間
v-bind:src="url" / :src="url"属性バインディング
v-on:click="handler" / @click="handler"イベントリスナー
v-if="condition"条件付きレンダリング
v-for="item in items"リストレンダリング
v-model="value"双方向バインディング
<template>
  <img :src="imageUrl" :alt="title">
  <button @click="handleClick">Click</button>
  
  <div v-if="isVisible">表示中</div>
  <div v-else>非表示</div>

  <ul>
    <li v-for="item in items" :key="item.id">
      {{ item.name }}
    </li>
  </ul>

  <input v-model="text" type="text">
</template>

実践メモ: v-forには必ず:keyを指定します。一意なキーがないと、リストの更新時にパフォーマンスが劣化したり、状態が崩れたりします。

ライフサイクルフック

フックタイミング
onBeforeMountマウント前
onMountedマウント後(DOM 操作可能)
onBeforeUpdate更新前
onUpdated更新後
onBeforeUnmountアンマウント前
onUnmountedアンマウント後
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'

const count = ref(0)
let timer = null

onMounted(() => {
  console.log('Component mounted')
  timer = setInterval(() => {
    count.value++
  }, 1000)
})

onUnmounted(() => {
  console.log('Component unmounted')
  clearInterval(timer)
})
</script>

ポイント: onMountedで DOM が利用可能になります。API 呼び出しや DOM 操作はここで実行します。onUnmountedタイマーやイベントリスナーをクリーンアップします。

コンポーネント Props と Emit

<!-- Child.vue -->
<script setup>
import { defineProps, defineEmits } from 'vue'

const props = defineProps({
  title: String,
  count: {
    type: Number,
    default: 0
  }
})

const emit = defineEmits(['increment', 'decrement'])

function handleClick() {
  emit('increment', props.count + 1)
}
</script>

<template>
  <h2>{{ title }}</h2>
  <p>{{ count }}</p>
  <button @click="handleClick">+1</button>
</template>
<!-- Parent.vue -->
<script setup>
import { ref } from 'vue'
import Child from './Child.vue'

const count = ref(0)

function onIncrement(newValue) {
  count.value = newValue
}
</script>

<template>
  <Child :title="My Counter" :count="count" @increment="onIncrement" />
</template>

注意: propsは読み取り専用です。子コンポーネントから直接変更できません。親に変更を通知する場合はemitを使います。

Composables(再利用ロジック)

// useCounter.js
import { ref } from 'vue'

export function useCounter(initialValue = 0) {
  const count = ref(initialValue)

  function increment() {
    count.value++
  }

  function decrement() {
    count.value--
  }

  return {
    count,
    increment,
    decrement
  }
}
<script setup>
import { useCounter } from './useCounter'

const { count, increment, decrement } = useCounter(10)
</script>

<template>
  <p>{{ count }}</p>
  <button @click="increment">+</button>
  <button @click="decrement">-</button>
</template>

実践メモ: Composablesuseプレフィックスの関数で、状態とロジックを再利用できます。複数コンポーネントで同じロジックを使う場合に便利です。

Vue Router

API説明
<router-link to="/path">ナビゲーションリンク
<router-view />ルートコンポーネント表示
useRouter()ルーターインスタンス取得
useRoute()現在のルート情報取得
// router.js
import { createRouter, createWebHistory } from 'vue-router'
import Home from './views/Home.vue'
import About from './views/About.vue'

const routes = [
  { path: '/', component: Home },
  { path: '/about', component: About },
  { path: '/user/:id', component: User }
]

export const router = createRouter({
  history: createWebHistory(),
  routes
})
<script setup>
import { useRouter, useRoute } from 'vue-router'

const router = useRouter()
const route = useRoute()

function goToAbout() {
  router.push('/about')
}

console.log(route.params.id)
</script>

<template>
  <nav>
    <router-link to="/">Home</router-link>
    <router-link to="/about">About</router-link>
  </nav>
  <router-view />
</template>

ポイント: useRouterでナビゲーション操作、useRouteでパラメータ取得します。router.pushプログラムから遷移するときに使います。

Pinia(状態管理)

// stores/counter.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

export const useCounterStore = defineStore('counter', () => {
  const count = ref(0)
  const doubled = computed(() => count.value * 2)

  function increment() {
    count.value++
  }

  return {
    count,
    doubled,
    increment
  }
})
<script setup>
import { useCounterStore } from './stores/counter'

const counter = useCounterStore()
</script>

<template>
  <p>{{ counter.count }} - {{ counter.doubled }}</p>
  <button @click="counter.increment">+1</button>
</template>

実践メモ: Piniaは Vuex の後継で、Composition API と同じ記法で書けます。型推論も強力で、TypeScript との相性が良いです。

フォームバインディング

要素v-model の動作
<input type="text">value 属性をバインド
<textarea>value 属性をバインド
<input type="checkbox">checked 属性をバインド
<input type="radio">checked 属性をバインド
<select>value 属性をバインド
<script setup>
import { ref } from 'vue'

const text = ref('')
const checked = ref(false)
const selected = ref('')
</script>

<template>
  <input v-model="text" type="text">
  <input v-model="checked" type="checkbox">
  <select v-model="selected">
    <option value="A">Option A</option>
    <option value="B">Option B</option>
  </select>
</template>
修飾子説明
v-model.lazychange イベントで同期(input ではなく)
v-model.number数値型に自動変換
v-model.trim前後の空白を削除

ポイント: v-model.numberを使うと、input[type="number"]の値が文字列ではなく数値になります。計算処理で型変換が不要になります。

クラスとスタイルバインディング

<template>
  <div :class="{ active: isActive, 'text-danger': hasError }">
    テキスト
  </div>

  <div :class="[baseClass, { active: isActive }]">
    配列形式
  </div>

  <div :style="{ color: textColor, fontSize: fontSize + 'px' }">
    スタイル
  </div>
</template>

実践メモ: :classのオブジェクト構文で、条件に応じてクラスを付け外しできます。静的クラスと併用も可能です。

スロット

<!-- Card.vue -->
<template>
  <div class="card">
    <div class="card-header">
      <slot name="header">デフォルトヘッダー</slot>
    </div>
    <div class="card-body">
      <slot>デフォルト本文</slot>
    </div>
  </div>
</template>
<!-- 使用側 -->
<Card>
  <template #header>
    <h2>タイトル</h2>
  </template>
  <p>本文テキスト</p>
</Card>

ポイント: スロットでコンポーネントの一部を親から注入できます。nameで複数のスロットを定義できます。

参考リソース

関連記事

この技術を体系的に学びたいですか?

未来学では東証プライム上場企業のITエンジニアが24時間サポート。月額24,800円から、退会金0円のオンラインIT塾です。

メールで無料相談する
← 一覧に戻る