TechBlog

Vue.js入門:Composition APIでモダンなフロントエンド開発を始めよう

by あくえり
#Vue.js #JavaScript #フロントエンド #Composition API #フレームワーク
Vue.js入門ガイド
目次

Vue 3とは

Vue.jsはEvan You氏が開発したJavaScriptフレームワークです。2020年にリリースされたVue 3ではComposition APIが導入され、ロジックの再利用性・TypeScriptとの親和性が大幅に向上しました。

ReactとVueを比較すると、以下のような特徴があります。

項目Vue 3React 18
テンプレート記法HTML拡張構文(.vueファイル)JSX
リアクティブref / reactiveuseState / useReducer
学習コスト比較的低い中程度
公式ルーターVue RouterReact Router(サードパーティ)
状態管理PiniaRedux / Zustand
採用企業Alibaba, Nintendo, GitLabMeta, Airbnb, Netflix

Vueは「テンプレートとロジックが同じファイル(SFC: Single File Component)に収まる」という書きやすさが特徴で、チーム全員がJSXを知らなくても参加しやすいです。

プロジェクトのセットアップ

Vue 3の公式推奨ツールはcreate-vueです(ViteベースでVue公式のスキャフォールドツール)。

# プロジェクト作成
npm create vue@latest my-vue-app

# 対話式で設定を選択
# ✔ Add TypeScript? → Yes
# ✔ Add Vue Router? → Yes
# ✔ Add Pinia? → Yes(状態管理が必要な場合)
# ✔ Add ESLint? → Yes

cd my-vue-app
npm install
npm run dev
# http://localhost:5173 で起動

src/ディレクトリ構成は以下が標準です。

src/
├── components/    # 再利用可能なUIコンポーネント
├── views/         # ページ単位のコンポーネント
├── router/        # Vue Routerの設定
├── stores/        # Piniaストア
├── composables/   # Composition API関数(ロジック再利用)
└── App.vue        # ルートコンポーネント

テンプレート構文の基本

Vue SFC(Single File Component)は<template><script><style>の3ブロックで構成されます。

<template>
  <div class="container">
    <!-- 変数の展開 -->
    <p>{{ message }}</p>

    <!-- v-bind: 属性バインディング(省略形は :) -->
    <img :src="imageUrl" :alt="altText" />

    <!-- v-on: イベントバインディング(省略形は @) -->
    <button @click="handleClick">クリック</button>

    <!-- v-if / v-else: 条件レンダリング -->
    <p v-if="isLoggedIn">ようこそ!</p>
    <p v-else>ログインしてください</p>

    <!-- v-for: リストレンダリング(:keyは必須) -->
    <ul>
      <li v-for="item in items" :key="item.id">
        {{ item.name }}
      </li>
    </ul>

    <!-- v-model: 双方向バインディング -->
    <input v-model="inputValue" placeholder="入力してください" />
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'

const message = ref('Hello, Vue 3!')
const imageUrl = ref('/img/logo.png')
const altText = ref('ロゴ')
const isLoggedIn = ref(false)
const items = ref([
  { id: 1, name: 'りんご' },
  { id: 2, name: 'バナナ' },
])
const inputValue = ref('')

function handleClick() {
  alert('クリックされました!')
}
</script>

<script setup>はVue 3のSyntax Sugarで、setup()関数の返り値を自動的にテンプレートに公開します。

Composition APIのリアクティブ

ref — プリミティブ値のリアクティブ

import { ref } from 'vue'

const count = ref(0)
const name = ref('Vue')

// 値を読む・書くには .value を使う(テンプレート内は不要)
console.log(count.value)  // 0
count.value++             // 1

reactive — オブジェクトのリアクティブ

import { reactive } from 'vue'

const state = reactive({
  count: 0,
  user: {
    name: 'あくえり',
    age: 25,
  }
})

// reactive はオブジェクトそのまま参照できる(.value 不要)
state.count++
state.user.name = '新しい名前'

computed — 算出プロパティ

import { ref, computed } from 'vue'

const price = ref(1000)
const quantity = ref(3)

// 依存する値が変わったときのみ再計算される
const total = computed(() => price.value * quantity.value)

console.log(total.value)  // 3000
price.value = 2000
console.log(total.value)  // 6000

watch — 値の変更を監視

import { ref, watch } from 'vue'

const searchQuery = ref('')

// searchQueryが変わるたびに実行
watch(searchQuery, (newValue, oldValue) => {
  console.log(`検索: ${oldValue} → ${newValue}`)
  // API呼び出しなど非同期処理を行う
})

コンポーネント設計:props と emit

親から子へはprops、子から親へはemitでデータを渡します。

子コンポーネント(TodoItem.vue)

<template>
  <li class="todo-item">
    <input
      type="checkbox"
      :checked="todo.completed"
      @change="emit('toggle', todo.id)"
    />
    <span :class="{ completed: todo.completed }">{{ todo.text }}</span>
    <button @click="emit('remove', todo.id)">削除</button>
  </li>
</template>

<script setup lang="ts">
// Props定義
const props = defineProps<{
  todo: {
    id: number
    text: string
    completed: boolean
  }
}>()

// Emit定義
const emit = defineEmits<{
  toggle: [id: number]
  remove: [id: number]
}>()
</script>

<style scoped>
.completed {
  text-decoration: line-through;
  color: #94a3b8;
}
</style>

親コンポーネント(TodoApp.vue)

<template>
  <div class="todo-app">
    <h1>Todoアプリ</h1>

    <!-- 新規追加フォーム -->
    <form @submit.prevent="addTodo">
      <input v-model="newTodoText" placeholder="Todoを入力..." />
      <button type="submit">追加</button>
    </form>

    <!-- 統計 -->
    <p>{{ remainingCount }}件が未完了 / 合計{{ todos.length }}件</p>

    <!-- Todoリスト -->
    <ul>
      <TodoItem
        v-for="todo in todos"
        :key="todo.id"
        :todo="todo"
        @toggle="toggleTodo"
        @remove="removeTodo"
      />
    </ul>
  </div>
</template>

<script setup lang="ts">
import { ref, computed } from 'vue'
import TodoItem from './TodoItem.vue'

interface Todo {
  id: number
  text: string
  completed: boolean
}

const todos = ref<Todo[]>([
  { id: 1, text: 'Vue 3を学ぶ', completed: false },
  { id: 2, text: 'Composition APIを理解する', completed: true },
])

const newTodoText = ref('')
let nextId = 3

// 算出プロパティ
const remainingCount = computed(
  () => todos.value.filter(t => !t.completed).length
)

// Todo追加
function addTodo() {
  const text = newTodoText.value.trim()
  if (!text) return
  todos.value.push({ id: nextId++, text, completed: false })
  newTodoText.value = ''
}

// 完了切り替え
function toggleTodo(id: number) {
  const todo = todos.value.find(t => t.id === id)
  if (todo) todo.completed = !todo.completed
}

// 削除
function removeTodo(id: number) {
  todos.value = todos.value.filter(t => t.id !== id)
}
</script>

Composable — ロジックの再利用

Composition APIの大きなメリットはロジックを関数として切り出せる点です。

// src/composables/useFetch.ts
import { ref, onMounted } from 'vue'

export function useFetch<T>(url: string) {
  const data = ref<T | null>(null)
  const loading = ref(true)
  const error = ref<string | null>(null)

  async function fetchData() {
    try {
      loading.value = true
      const response = await fetch(url)
      if (!response.ok) throw new Error(`HTTP error: ${response.status}`)
      data.value = await response.json()
    } catch (e) {
      error.value = e instanceof Error ? e.message : '取得に失敗しました'
    } finally {
      loading.value = false
    }
  }

  onMounted(fetchData)

  return { data, loading, error, refetch: fetchData }
}

使う側のコンポーネントはこのように呼び出すだけです。

<script setup lang="ts">
import { useFetch } from '@/composables/useFetch'

const { data, loading, error } = useFetch<{ name: string }[]>(
  'https://api.example.com/users'
)
</script>

<template>
  <div>
    <p v-if="loading">読み込み中...</p>
    <p v-else-if="error">エラー: {{ error }}</p>
    <ul v-else>
      <li v-for="user in data" :key="user.name">{{ user.name }}</li>
    </ul>
  </div>
</template>

Vue.js 3 フロントエンド開発の教科書

Vue 3 + TypeScriptの実践的な使い方をComposition APIを中心に解説。Pinia・Vue Routerも網羅しており、現場レベルのVue開発スキルを習得できます。

※ アフィリエイトリンクを含みます

まとめ

Vue 3 + Composition APIの要点を整理します。

  • ref: プリミティブ値をリアクティブに。値アクセスは.value
  • reactive: オブジェクトをリアクティブに。.value不要
  • computed: 依存値が変わった時だけ再計算
  • watch: 値の変化を監視して副作用を実行
  • props/emit: 親子コンポーネント間のデータフロー
  • Composable: ロジックを関数に切り出して再利用

<script setup>構文を使うと記述量が減り、TypeScriptとの統合もスムーズです。まずは公式ガイドのチュートリアルでTodoアプリを作り、その後でVue Routerによるページ遷移、Piniaによる状態管理に進むのがおすすめの学習ルートです。

共有: