Vue.jsとは
Vue.jsは、Webページの画面を部品に分け、データの変化をUIへ反映しやすくするJavaScriptフレームワークです。HTMLに近いテンプレート、コンポーネント、リアクティビティを組み合わせて、入力フォームや管理画面、シングルページアプリケーションを作れます。
- ViteでVueプロジェクトを起動する
- テンプレートとイベントで小さなUIを作る
ref・computed・watchで状態を扱う- コンポーネント、フォーム、API通信へ進む
- Router・Pinia・テストでアプリを整理する
Vueでは、状態から画面が決まる関係を宣言的に書きます。DOMを一つずつ探して書き換えるのではなく、データを変更するとVueが必要な部分を更新します。
環境構築
現在のVue公式ガイドに合わせ、Viteでプロジェクトを作成します。Node.jsをインストールしたターミナルで次のコマンドを実行してください。
npm create vue@latest vue-practice
cd vue-practice
npm install
npm run devLocal: http://localhost:5173/質問画面では、初めてならTypeScript、Router、Pinia、テストは一度Noにしても構いません。この講座の後半では、それぞれを使う理由と導入方法を扱います。最初はsrc/App.vueを編集し、ブラウザーを更新して結果を確認します。
node --versionとnpm --versionでNode.jsが認識されるか、コマンドを実行したフォルダーが正しいかを確認します。エラーの最後の数行を読み、依存関係のインストールを省略していないか確認してください。テンプレートと基本文法
Vueのテンプレートでは、二重中括弧で値を表示し、v-bindやv-onなどのディレクティブで属性やイベントを指定します。短縮記法もありますが、最初は意味を理解してから使いましょう。
<script setup>
import { ref } from 'vue'
const name = ref('Mika')
const imageUrl = 'https://example.com/avatar.png'
</script>
<template>
<main>
<h1>こんにちは、{{ name }}さん</h1>
<img :src="imageUrl" alt="プロフィール画像">
</main>
</template>こんにちは、Mikaさん
画像が指定したURLから読み込まれる:srcはv-bind:src、@clickはv-on:clickの短縮記法です。HTML属性へ文字列を固定で書くか、JavaScriptの値を結び付けるかを意識してください。
<script setup>
import { ref } from 'vue'
const visible = ref(true)
const fruits = ref(['りんご', 'みかん', 'ぶどう'])
function toggle() { visible.value = !visible.value }
</script>
<template>
<button @click="toggle">表示を切り替える</button>
<p v-if="visible">表示中です</p>
<ul><li v-for="fruit in fruits" :key="fruit">{{ fruit }}</li></ul>
</template>v-ifは条件に応じて要素を作ったり外したりします。v-showは要素を残したまま表示・非表示を切り替えます。v-forでは、並べ替えや削除に対応できる安定した:keyを指定します。
リアクティビティと状態
Vueのリアクティビティは、状態を読み取った場所を追跡し、状態が変更されたときに必要な処理を実行する仕組みです。プリミティブ値にはref、複数のプロパティを持つオブジェクトにはreactiveを使います。
<script setup>
import { ref, computed } from 'vue'
const price = ref(1200)
const quantity = ref(2)
const total = computed(() => price.value * quantity.value)
</script>
<template>
<input v-model.number="quantity" type="number" min="1">
<p>合計: {{ total }}円</p>
</template>初期表示: 合計: 2400円
数量を3に変更: 合計: 3600円テンプレート中ではrefの.valueが自動的に展開されますが、JavaScriptの処理中ではquantity.valueと書きます。計算結果はcomputedにし、ボタン操作や通信などの副作用を入れないのが基本です。
別の状態から値を計算するなら
computed、状態の変化をきっかけに通信・localStorage・ログ出力など外部へ作用させるならwatchを使います。import { ref, watch, onMounted } from 'vue'
const keyword = ref('')
watch(keyword, (newKeyword, oldKeyword) => {
console.log(`${oldKeyword} から ${newKeyword} へ変更`)
})
onMounted(() => {
console.log('画面がDOMへ追加されました')
})onMountedは初回描画後、onUnmountedはコンポーネントが破棄されるときに実行します。タイマーやイベント購読を開始したら、終了時に解除することも忘れないでください。
コンポーネントとProps
画面を再利用できる部品へ分けると、ファイルが読みやすくなり、テストもしやすくなります。親から子へ値を渡すのがProps、子から親へ出来事を伝えるのがイベントです。
<script setup>
defineProps({
todo: { type: Object, required: true }
})
const emit = defineEmits(['remove'])
</script>
<template>
<li>
{{ todo.text }}
<button @click="emit('remove', todo.id)">削除</button>
</li>
</template><TodoItem
v-for="todo in todos"
:key="todo.id"
:todo="todo"
@remove="removeTodo"
/>子コンポーネントがPropsを直接書き換えるのではなく、イベントで親へ依頼します。データの所有者を一つに決めると、どこで値が変わったのか追跡しやすくなります。さらに共通の外枠を差し替えたい場合は<slot>を使います。
フォームとAPI通信
v-modelは入力値と状態を同期します。v-model.trimやv-model.numberで入力値を整えられますが、送信時にも必ず検証してください。
const title = ref('')
const message = ref('')
function submit() {
const value = title.value.trim()
if (value === '') {
message.value = 'タイトルを入力してください'
return
}
message.value = `「${value}」を登録しました`
}空欄で送信: タイトルを入力してください
Vue入門で送信: 「Vue入門」を登録しましたAPI通信では、読み込み中・成功・失敗を別の状態として扱います。HTTPステータスを確認し、コンポーネントが破棄されたときに通信を中断できるようにすると、より安全です。
const items = ref([])
const status = ref('idle')
const errorMessage = ref('')
async function loadItems() {
status.value = 'loading'
try {
const response = await fetch('/api/items')
if (response.ok === false) throw new Error('HTTPエラー')
items.value = await response.json()
status.value = 'success'
} catch (error) {
errorMessage.value = error.message
status.value = 'error'
}
}Router・Pinia・テスト
複数画面のSPAには公式ルーターのVue Router、複数コンポーネントで共有する状態にはPiniaを使います。どちらも最初から導入するのではなく、ページ間の移動や共有状態が必要になった段階で追加します。
import { createRouter, createWebHistory } from 'vue-router'
import Home from './views/Home.vue'
import About from './views/About.vue'
export default createRouter({
history: createWebHistory(),
routes: [
{ path: '/', component: Home },
{ path: '/about', component: About }
]
})import { expect, test } from 'vitest'
function total(price, quantity) {
return price * quantity
}
test('合計金額を計算する', () => {
expect(total(1200, 2)).toBe(2400)
})Piniaでは状態・getter・actionをストアにまとめます。コンポーネントの表示、ユーザー操作、API通信の責務を分け、純粋な計算は小さな関数としてテストすると、変更の影響を確認しやすくなります。
次に学ぶこと
各講座で作ったカウンター、Todo、フォームを組み合わせ、入力・一覧・詳細表示のある小さなアプリを作ってみましょう。その後、TypeScriptでPropsやAPIデータの型を付け、アクセシビリティ、認証、デプロイへ進みます。