Vue.js
エンジニアのためのWebチートシート
Vue.jsはプログレッシブJavaScriptフレームワークです。 Composition API、テンプレート構文、リアクティビティ、コンポーネントなどをチートシートにまとめました。
セットアップ
プロジェクト作成
create-vueでプロジェクトを作成します。
npm create vue@latest my-app cd my-app npm install npm run dev
SFC(単一ファイルコンポーネント)
.vueファイルの基本構造です。
<script setup lang="ts"> import { ref } from 'vue' const count = ref(0) </script> <template> <button @click="count++"> Count: {{ count }} </button> </template> <style scoped> button { font-size: 1.2rem; } </style>
テンプレート構文
テキスト展開
二重波括弧でデータをテンプレートに展開します。
<template> <p>{{ message }}</p> <p>{{ count + 1 }}</p> <p>{{ ok ? 'YES' : 'NO' }}</p> <p v-html="rawHtml"></p> </template>
ディレクティブ
v-接頭辞付きの特別な属性です。
<!-- 属性バインディング --> <img v-bind:src="imageUrl"> <img :src="imageUrl"> <!-- 省略形 --> <div :class="{ active: isActive }"></div> <div :style="{ color: textColor }"></div> <!-- 動的属性 --> <a :[attrName]="value">Link</a>
イベントハンドリング
v-onまたは@でイベントを処理します。
<button v-on:click="handleClick">Click</button> <button @click="handleClick">Click</button> <button @click="count++">Increment</button> <!-- 修飾子 --> <form @submit.prevent="onSubmit">Submit</form> <input @keyup.enter="search"> <button @click.once="doOnce">Once</button> <div @click.stop="stopPropagation">Stop</div>
条件付きレンダリング
v-if / v-else / v-showで条件分岐します。
<div v-if="type === 'A'">A</div> <div v-else-if="type === 'B'">B</div> <div v-else>Other</div> <!-- v-show (CSS display切替) --> <div v-show="isVisible">Visible</div> <!-- v-if: DOM追加/削除 --> <!-- v-show: display: none切替 -->
リストレンダリング
v-forでリストをレンダリングします。
<ul> <li v-for="item in items" :key="item.id"> {{ item.name }} </li> </ul> <!-- index付き --> <li v-for="(item, index) in items" :key="item.id"> {{ index }}: {{ item.name }} </li> <!-- オブジェクト --> <div v-for="(value, key) in obj" :key="key"> {{ key }}: {{ value }} </div>
リアクティビティ
ref / reactive
リアクティブな状態を定義します。
import { ref, reactive } from 'vue' // ref: プリミティブ値に使用(.valueでアクセス) const count = ref(0) count.value++ const name = ref('Vue') console.log(name.value) // reactive: オブジェクトに使用 const state = reactive({ count: 0, name: 'Vue' }) state.count++ // .value不要
v-model(双方向バインディング)
フォーム入力とデータを双方向にバインドします。
<script setup> import { ref } from 'vue' const text = ref('') const checked = ref(false) const selected = ref('') </script> <template> <input v-model="text"> <input v-model.trim="text"> <!-- 前後空白除去 --> <input v-model.number="age"> <!-- 数値変換 --> <input v-model.lazy="text"> <!-- change時に更新 --> <input type="checkbox" v-model="checked"> <select v-model="selected"> <option value="a">A</option> <option value="b">B</option> </select> </template>
Computed & Watch
computed(算出プロパティ)
依存関係に基づいてキャッシュされた値を返します。
import { ref, computed } from 'vue' const firstName = ref('Taro') const lastName = ref('Yamada') const fullName = computed(() => { return `${firstName.value} ${lastName.value}` }) // Writable computed const fullName2 = computed({ get: () => `${firstName.value} ${lastName.value}`, set: (val) => { [firstName.value, lastName.value] = val.split(' ') } })
watch / watchEffect
リアクティブな値の変更を監視します。
import { ref, watch, watchEffect } from 'vue' const count = ref(0) // watch: 特定の値を監視 watch(count, (newVal, oldVal) => { console.log(`${oldVal} -> ${newVal}`) }) // 即時実行 watch(count, (val) => { ... }, { immediate: true }) // deep watch watch(obj, (val) => { ... }, { deep: true }) // watchEffect: 依存を自動追跡 watchEffect(() => { console.log(count.value) })
コンポーネント
Props
親コンポーネントから子にデータを渡します。
// 子コンポーネント <script setup lang="ts"> const props = defineProps<{ title: string count?: number }>() // デフォルト値 const props = withDefaults(defineProps<{ title: string count?: number }>(), { count: 0 }) </script> <!-- 親コンポーネント --> <ChildComponent title="Hello" :count="5" />
Emit(イベント発火)
子コンポーネントから親にイベントを通知します。
// 子コンポーネント <script setup lang="ts"> const emit = defineEmits<{ (e: 'update', value: string): void (e: 'delete', id: number): void }>() const handleClick = () => { emit('update', 'new value') } </script> <!-- 親コンポーネント --> <ChildComponent @update="handleUpdate" @delete="handleDelete" />
スロット
コンポーネントにコンテンツを挿入します。
<!-- 子: Card.vue --> <template> <div class="card"> <slot></slot> <!-- デフォルト --> <slot name="header"></slot> <!-- 名前付き --> <slot name="footer" :data="items"></slot> </div> </template> <!-- 親 --> <Card> <p>Default content</p> <template #header>Header</template> <template #footer="{ data }"> {{ data.length }} items </template> </Card>
Provide / Inject
深い階層のコンポーネント間でデータを共有します。
// 親コンポーネント import { provide, ref } from 'vue' const theme = ref('dark') provide('theme', theme) // 子孫コンポーネント(何階層でもOK) import { inject } from 'vue' const theme = inject('theme', 'light') // default: 'light'
ライフサイクル
ライフサイクルフック
コンポーネントのライフサイクルに応じた処理を行います。
import { onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted } from 'vue' onBeforeMount(() => { /* DOM生成前 */ }) onMounted(() => { /* DOM生成後(APIコール、DOM操作) */ })onBeforeUpdate(() => { /* リアクティブデータ変更後、DOM更新前 */ }) onUpdated(() => { /* DOM更新後 */ })onBeforeUnmount(() => { /* コンポーネント破棄前(クリーンアップ) */ }) onUnmounted(() => { /* コンポーネント破棄後 */ })
Composables
Composable関数
ロジックを再利用可能な関数に切り出します。
// composables/useMouse.ts import { ref, onMounted, onUnmounted } from 'vue' export function useMouse() { const x = ref(0) const y = ref(0) const update = (e: MouseEvent) => { x.value = e.pageX; y.value = e.pageY } onMounted(() => window.addEventListener('mousemove', update)) onUnmounted(() => window.removeEventListener('mousemove', update)) return { x, y } }<!-- Usage in component --> <script setup> import { useMouse } from './composables/useMouse' const { x, y } = useMouse() </script>
Vue Router
Vue Routerの基本
ルーティングの基本的な設定です。
import { createRouter, createWebHistory } from 'vue-router' const router = createRouter({ history: createWebHistory(), routes: [ { path: '/', component: Home }, { path: '/about', component: About }, { path: '/user/:id', component: User }, { path: '/:pathMatch(.*)*', component: NotFound }, ] }) // Template <router-link to="/">Home</router-link> <router-link :to="{ name: 'user', params: { id: 1 }}"> User </router-link> <router-view />
ナビゲーションガード
ルート遷移を制御します。
// Global guard router.beforeEach((to, from) => { if (to.meta.requiresAuth && !isAuthenticated()) return '/login' }) // Route-level guard { path: '/admin', component: Admin, beforeEnter: (to) => { if (!isAdmin()) return '/forbidden' } }// useRouter / useRoute import { useRouter, useRoute } from 'vue-router' const router = useRouter() const route = useRoute() router.push('/about') console.log(route.params.id)