一、HTTPリクエスト処理
1. APIインタフェースの定義
import request from '@/utils/request'
export const fetchUserList = (query: UserQuery, payload: UserPayload) =>
request({
url: '/api/v1/users',
method: 'post',
query,
payload
})
2. リクエストインターセプタ
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'
const httpClient: AxiosInstance = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL,
timeout: 10000
})
// リクエストインターセプタ
httpClient.interceptors.request.use(
(config: AxiosRequestConfig) => {
const authToken = localStorage.getItem('access_token')
if (authToken) {
config.headers?.set('Authorization', `Bearer ${authToken}`)
}
return config
},
(error: Error) => {
return Promise.reject(error)
}
)
// レスポンスインターセプタ
httpClient.interceptors.response.use(
(response: AxiosResponse) => {
return response.data
},
(error: unknown) => {
return Promise.reject(error)
}
)
interface ApiResponse<T> {
status: number
result: T
message: string
}
export function apiClient<T>(config: AxiosRequestConfig): Promise<ApiResponse<T>> {
return httpClient(config) as Promise<ApiResponse<T>>
}
二、コンポーネント実装
1. コンポーネント基本形
import { Component, Vue } from 'vue-property-decorator'
@Component({
name: 'UserProfile'
})
export default class UserProfile extends Vue {
private userName: string = ''
private isLoading: boolean = false
private async loadUserData(userId: string): Promise<void> {
this.isLoading = true
try {
const response = await fetchUserData(userId)
this.userName = response.result.name
} finally {
this.isLoading = false
}
}
protected mounted(): void {
this.loadUserData('123')
}
}
2. ウォッチャ機能
import { Component, Vue, Watch } from 'vue-property-decorator'
@Component({
name: 'DataWatcher'
})
export default class DataWatcher extends Vue {
private searchKeyword: string = ''
@Watch('searchKeyword')
private onKeywordChanged(newValue: string, oldValue: string): void {
this.fetchSearchResults()
}
private fetchSearchResults(): void {
console.log('検索実行:', this.searchKeyword)
}
}
3. プロパティ定義
import { Component, Prop, Vue } from 'vue-property-decorator'
@Component({
name: 'ConfirmDialog'
})
export default class ConfirmDialog extends Vue {
@Prop({
type: Boolean,
required: true,
default: false,
validator: (value: unknown): boolean => typeof value === 'boolean'
})
private isVisible!: boolean
@Prop({
type: String,
default: '確認'
})
private dialogTitle!: string
}
4. イベントエミット
import { Component, Vue } from 'vue-property-decorator'
@Component({
name: 'ActionButton'
})
export default class ActionButton extends Vue {
private handleClick(): void {
this.$emit('button-clicked', { timestamp: Date.now() })
}
}
5. ルーティング情報へのアクセス
import { Component, Vue } from 'vue-property-decorator'
@Component({
name: 'PageNavigator'
})
export default class PageNavigator extends Vue {
private navigateToDetail(): void {
const currentRoute = this.$route
const currentParams = this.$route.params
this.$router.push({
path: `/details/${currentParams.id}`,
query: { source: 'list' }
})
}
}
6. 算出プロパティとストア連携
import { Component, Vue } from 'vue-property-decorator'
import { UserStore } from '@/store/modules/user'
@Component({
name: 'UserInfo'
})
export default class UserInfo extends Vue {
get userDisplayName(): string {
return UserStore.fullName
}
}
7. フィルター機能
import { Component, Vue } from 'vue-property-decorator'
@Component({
name: 'FormatHelpers',
filters: {
currencyJPY(value: number): string {
return new Intl.NumberFormat('ja-JP', {
style: 'currency',
currency: 'JPY'
}).format(value)
},
truncateText(text: string, length: number): string {
return text.length > length ? text.slice(0, length) + '...' : text
}
}
})
export default class FormatHelpers extends Vue {}
}
三、ルーター設定
1. ルーター初期設定
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
import Layout from '@/components/Layout/index.vue'
const routes: RouteRecordRaw[] = [
{
path: '/dashboard',
component: Layout,
redirect: '/dashboard/overview',
children: [
{
path: 'overview',
name: 'DashboardOverview',
component: () => import('@/views/dashboard/Overview.vue'),
meta: {
title: 'ダッシュボード',
icon: 'dashboard',
keepAlive: true
}
}
]
}
]
const router = createRouter({
history: createWebHistory(),
scrollBehavior: (to, from, savedPosition) => {
if (savedPosition) {
return savedPosition
}
return { top: 0, left: 0 }
},
routes
})
export default router
2. ナビゲーションガード
import router from '@/router'
import { NavigationGuardNext, RouteLocationNormalized } from 'vue-router'
import { AuthService } from '@/services/auth'
router.beforeEach(
async (
to: RouteLocationNormalized,
from: RouteLocationNormalized,
next: NavigationGuardNext
): Promise<void> {
const requiresAuth = to.meta.requiresAuth !== false
if (requiresAuth && !AuthService.isAuthenticated()) {
next({ path: '/login', query: { redirect: to.fullPath } })
return
}
next()
}
)
router.afterEach((to: RouteLocationNormalized): void => {
document.title = to.meta.title as string || 'アプリケーション'
})
3. アプリケーション起動設定
import { createApp } from 'vue'
import App from '@/App.vue'
import router from '@/router'
import { setupStore } from '@/store'
const app = createApp(App)
setupStore(app)
app.use(router)
app.mount('#app')
4. ネストドルーターでのスクロール位置復元(Vue3)
<template>
<div ref="containerRef" class="page-container">
<header class="app-header">ヘッダー</header>
<main class="app-content">
<router-view v-slot="{ Component }">
<transition name="fade">
<keep-alive>
<component :is="Component" />
</keep-alive>
</transition>
</router-view>
</main>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { onBeforeRouteUpdate } from 'vue-router'
const containerRef = ref<HTMLElement | null>(null)
onBeforeRouteUpdate((to, from) => {
const currentRoute = from.matched[from.matched.length - 1]
if (currentRoute) {
currentRoute.meta.savedScrollTop = containerRef.value?.scrollTop || 0
}
if (to.meta.keepAlive && typeof to.meta.savedScrollTop === 'number') {
containerRef.value!.scrollTop = to.meta.savedScrollTop
} else {
containerRef.value!.scrollTop = 0
}
})
</script>
<style scoped>
.page-container {
height: 100vh;
overflow-y: auto;
}
</style>
四、ステート管理
1. ルートストア定義
import { createStore } from 'vuex'
import { IApplicationState } from './modules/application'
export interface RootState {
application: IApplicationState
}
export default createStore<RootState>({
modules: {
application
}
})
2. モジュール実装
import { Module, VuexModule, Mutation, Action, getModule } from 'vuex-module-decorators'
import store from '@/store'
export interface IApplicationState {
theme: 'light' | 'dark'
sidebarCollapsed: boolean
}
@Module({
dynamic: true,
store,
name: 'application'
})
class ApplicationModule extends VuexModule implements IApplicationState {
public theme: 'light' | 'dark' = 'light'
public sidebarCollapsed = false
get currentTheme(): 'light' | 'dark' {
return this.theme
}
@Mutation
SET_THEME(theme: 'light' | 'dark'): void {
this.theme = theme
}
@Mutation
TOGGLE_SIDEBAR(): void {
this.sidebarCollapsed = !this.sidebarCollapsed
}
@Action
public changeTheme(theme: 'light' | 'dark'): void {
this.SET_THEME(theme)
localStorage.setItem('theme', theme)
}
}
export const ApplicationStore = getModule(ApplicationModule)
3. ストア初期化
import { createApp } from 'vue'
import App from '@/App.vue'
import rootStore from '@/store'
const app = createApp(App)
app.use(rootStore)
app.mount('#app')