はじめに
Vue.jsって、なんだかんだ言って息が長いフレームワークなんですよね。
GitHubで52,300スター以上、毎月3,000万回以上のnpmダウンロード。「The Progressive JavaScript Framework」というキャッチコピー通り、小さく始めて徐々に拡張できるという設計思想が、多くの開発者に支持されています。
正直なところ、最初Vueを触ったときは「Reactの方がモダンでしょ」くらいに思っていたんですよ。でも実際に業務で使ってみると、このフレームワークの「わかりやすさ」と「とっつきやすさ」は本物だと感じました。30代になって思うのは、技術選定って「最先端かどうか」より「チーム全体で生産性が出るか」の方が大事だということ。
Vue.jsとは
Vue.js(ビュー・ジェイエス)は、Evan You氏が2014年に開発したUIフレームワークです。現在の最新バージョンはv3.5.25で、MITライセンスのオープンソースとして公開されています。
公式サイトでは3つの特徴を掲げています。
- Approachable(親しみやすい): 標準的なHTML、CSS、JavaScriptの知識があれば始められる
- Performant(高性能): 真のリアクティビティとコンパイラ最適化による高速な描画
- Versatile(多様性): ライブラリとして使うことも、フルスタックフレームワークとして使うこともできる
個人的には「親しみやすさ」が一番の強みだと思います。学習コストが低いので、チームに新しいメンバーが入ってもキャッチアップが早い。
特徴・メリット
1. 単一ファイルコンポーネント(SFC)が直感的
VueのSFC(Single File Component)は、HTML・CSS・JavaScriptを1つの.vueファイルにまとめて書けます。
これ、意外と馬鹿にできなくて、「このコンポーネントに関するものは全部このファイルにある」という安心感があるんですよね。Reactだとスタイルの書き方が複数あって悩むけど、Vueは<style scoped>で解決。シンプル一択です。
2. リアクティビティシステムが秀逸
Vue 3のリアクティビティは、Proxyベースで実装されていて、かなり効率的に動作します。refやreactiveを使えば、値の変更が自動的にUIに反映される。
個人的には、この「何も考えなくても反映される」感覚がVueの一番の魅力だと思います。
3. Composition APIでロジックの再利用が楽
Vue 3から導入されたComposition APIは、Reactのhooksに近い書き方ができます。従来のOptions APIも使えるので、既存プロジェクトとの互換性も保たれている。
ロジックをカスタムコンポーザブルとして切り出せるので、コードの再利用性がかなり上がります。
4. TypeScriptとの相性が良くなった
Vue 3はTypeScriptで書き直されており、型サポートが格段に向上しました。<script setup lang="ts">と書くだけで、TypeScriptが使える。
IDE(特にVSCode + Volar拡張)との連携も良くて、補完がバシバシ効きます。
5. エコシステムが充実
公式で以下のライブラリが提供されています。
- Vue Router: ルーティング
- Pinia: 状態管理(Vuexの後継)
- Vite: 高速なビルドツール(Evan You氏作)
- Nuxt: フルスタックフレームワーク
公式がメンテナンスしているので、バージョン互換性の心配が少ないのが嬉しいですね。
インストール方法
2025年現在、Vueプロジェクトを始めるならViteを使うのが一般的です。
# npm
npm create vue@latest
# pnpm
pnpm create vue@latest
# yarn
yarn create vue
対話形式でプロジェクト名やオプションを聞かれます。
✔ Project name: … my-vue-app
✔ Add TypeScript? … Yes
✔ Add JSX Support? … No
✔ Add Vue Router for Single Page Application development? … Yes
✔ Add Pinia for state management? … Yes
✔ Add Vitest for Unit Testing? … Yes
✔ Add an End-to-End Testing Solution? … No
✔ Add ESLint for code quality? … Yes
✔ Add Prettier for code formatting? … Yes
個人的には、TypeScript、Vue Router、Pinia、ESLint、Prettierは入れておくことをおすすめします。後から追加するより最初から入れた方が楽なので。
cd my-vue-app
npm install
npm run dev
これでhttp://localhost:5173に開発サーバーが立ち上がります。
基本的な使い方
Composition API + script setup
Vue 3で推奨されている書き方です。
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
// リアクティブな状態
const count = ref(0)
const message = ref('Hello Vue!')
// 算出プロパティ
const doubleCount = computed(() => count.value * 2)
// メソッド
const increment = () => {
count.value++
}
// ライフサイクルフック
onMounted(() => {
console.log('コンポーネントがマウントされました')
})
</script>
<template>
<div class="counter">
<p>{{ message }}</p>
<p>カウント: {{ count }}</p>
<p>2倍: {{ doubleCount }}</p>
<button @click="increment">+1</button>
</div>
</template>
<style scoped>
.counter {
padding: 20px;
text-align: center;
}
button {
padding: 8px 16px;
font-size: 16px;
cursor: pointer;
}
</style>
refで作った値は、<script>内では.valueでアクセスしますが、<template>内では自動的にアンラップされるので.valueは不要です。この辺、最初は混乱するかもしれませんが、慣れると自然に書けるようになります。
Props と Emit
コンポーネント間のデータのやり取りです。
<!-- 親コンポーネント: Parent.vue -->
<script setup lang="ts">
import { ref } from 'vue'
import ChildComponent from './ChildComponent.vue'
const parentMessage = ref('親からのメッセージ')
const handleUpdate = (newValue: string) => {
console.log('子から受け取った:', newValue)
}
</script>
<template>
<ChildComponent
:message="parentMessage"
@update="handleUpdate"
/>
</template>
<!-- 子コンポーネント: ChildComponent.vue -->
<script setup lang="ts">
// Propsの定義
const props = defineProps<{
message: string
}>()
// Emitの定義
const emit = defineEmits<{
update: [value: string]
}>()
const sendToParent = () => {
emit('update', '子からのデータ')
}
</script>
<template>
<div>
<p>{{ props.message }}</p>
<button @click="sendToParent">親に送信</button>
</div>
</template>
TypeScriptを使うと、PropsとEmitの型が明確になって、IDEの補完も効くようになります。
v-model による双方向バインディング
フォームの入力値を扱うときに便利です。
<script setup lang="ts">
import { ref } from 'vue'
const name = ref('')
const email = ref('')
const agreed = ref(false)
</script>
<template>
<form @submit.prevent="handleSubmit">
<input v-model="name" placeholder="名前" />
<input v-model="email" type="email" placeholder="メールアドレス" />
<label>
<input v-model="agreed" type="checkbox" />
利用規約に同意する
</label>
<button type="submit" :disabled="!agreed">送信</button>
</form>
</template>
v-modelを使えば、@inputと:valueを別々に書く必要がない。コスパ的に、この省力化は大きいです。
実践的なユースケース
カスタムコンポーザブル(ロジックの再利用)
データフェッチングのロジックを再利用可能な形で切り出せます。
// composables/useFetch.ts
import { ref, watchEffect } from 'vue'
export function useFetch<T>(url: string) {
const data = ref<T | null>(null)
const error = ref<Error | null>(null)
const loading = ref(true)
watchEffect(async () => {
loading.value = true
error.value = null
try {
const response = await fetch(url)
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
data.value = await response.json()
} catch (e) {
error.value = e as Error
} finally {
loading.value = false
}
})
return { data, error, loading }
}
<!-- 使用例 -->
<script setup lang="ts">
import { useFetch } from '@/composables/useFetch'
interface User {
id: number
name: string
email: string
}
const { data: users, loading, error } = useFetch<User[]>('/api/users')
</script>
<template>
<div v-if="loading">読み込み中...</div>
<div v-else-if="error">エラー: {{ error.message }}</div>
<ul v-else>
<li v-for="user in users" :key="user.id">
{{ user.name }}
</li>
</ul>
</template>
Reactのカスタムフックとほぼ同じ感覚で書けます。
Piniaで状態管理
グローバルな状態管理にはPiniaを使います。Vuexより直感的で、TypeScriptとの相性も良い。
// stores/counter.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useCounterStore = defineStore('counter', () => {
// state
const count = ref(0)
// getters
const doubleCount = computed(() => count.value * 2)
// actions
const increment = () => {
count.value++
}
const decrement = () => {
count.value--
}
return { count, doubleCount, increment, decrement }
})
<!-- 使用例 -->
<script setup lang="ts">
import { useCounterStore } from '@/stores/counter'
const counter = useCounterStore()
</script>
<template>
<div>
<p>カウント: {{ counter.count }}</p>
<p>2倍: {{ counter.doubleCount }}</p>
<button @click="counter.increment">+</button>
<button @click="counter.decrement">-</button>
</div>
</template>
非同期コンポーネント
大きなコンポーネントを遅延読み込みして、初期バンドルサイズを削減できます。
<script setup lang="ts">
import { defineAsyncComponent } from 'vue'
const HeavyComponent = defineAsyncComponent(() =>
import('./HeavyComponent.vue')
)
</script>
<template>
<Suspense>
<template #default>
<HeavyComponent />
</template>
<template #fallback>
<div>読み込み中...</div>
</template>
</Suspense>
</template>
Reactとの比較
よく聞かれるので、個人的な見解を書いておきます。
| 観点 | Vue.js | React |
|---|---|---|
| 学習コスト | 低い | やや高い |
| 自由度 | 中程度(公式がガイド) | 高い(選択肢が多い) |
| テンプレート | HTML拡張 | JSX |
| 状態管理 | Pinia(公式) | Redux/Zustand等(選択制) |
| 日本での採用 | 増加中 | 多い |
正直なところ、どちらも優秀で、プロジェクトの規模やチームのスキルセットで選べば良いと思います。
個人的には、「HTMLベースのテンプレートが好き」「公式が推奨するやり方に従いたい」「学習コストを抑えたい」ならVue、「JSXの柔軟性が欲しい」「Reactエコシステムを使いたい」「すでにReact経験者が多い」ならReact、という感じですね。
まとめ
Vue.jsの良さは、以下の点に集約されると思います。
- 学習コストが低く、チーム全体で生産性が出しやすい
- 単一ファイルコンポーネントで管理がシンプル
- Composition APIでモダンな書き方ができる
- 公式エコシステムが充実していて迷わない
- TypeScriptとの相性が良い
30代になって思うのは、「技術選定は政治」だということ。いくら個人的に好きなフレームワークがあっても、チームで採用できなければ意味がない。その点、Vueの「わかりやすさ」は大きな武器になります。
Reactしか触ったことがない人も、一度Vueを試してみることをおすすめします。視野が広がりますし、両方知っていると技術選定の引き出しが増える。時間のある週末に、公式チュートリアルをやってみるだけでも価値はありますよ。