Vue Router + TypeScript 路由類型
Vue Router 4 原生支援 TypeScript。本篇將介紹路由參數、導航守衛等類型定義。
一、 基本設定
1.1 安裝
bash
npm install vue-router1.2 定義路由
typescript
// router/index.ts
import {
createRouter,
createWebHistory,
type RouteRecordRaw,
} from 'vue-router';
import Home from '@/views/Home.vue';
import About from '@/views/About.vue';
const routes: RouteRecordRaw[] = [
{
path: '/',
name: 'home',
component: Home,
},
{
path: '/about',
name: 'about',
component: About,
},
];
const router = createRouter({
history: createWebHistory(),
routes,
});
export default router;1.3 使用
typescript
// main.ts
import { createApp } from 'vue';
import router from './router';
import App from './App.vue';
createApp(App).use(router).mount('#app');二、 路由參數類型
2.1 基本參數
typescript
const routes: RouteRecordRaw[] = [
{
path: '/user/:id',
name: 'user',
component: () => import('@/views/User.vue'),
},
];vue
<!-- User.vue -->
<script setup lang="ts">
import { useRoute } from "vue-router";
const route = useRoute();
// params 類型是 Record<string, string | string[]>
const userId = route.params.id as string;
</script>2.2 強類型路由參數
typescript
// 定義路由名稱和參數的映射
interface RouteParams {
home: undefined;
user: { id: string };
post: { id: string; slug?: string };
}
// 使用泛型函式
function useTypedRoute<T extends keyof RouteParams>() {
const route = useRoute();
return route as typeof route & { params: RouteParams[T] };
}
// 在元件中使用
const route = useTypedRoute<'user'>();
const userId = route.params.id; // string三、 Query 參數
3.1 基本用法
vue
<script setup lang="ts">
import { useRoute } from "vue-router";
const route = useRoute();
// query 類型是 Record<string, string | string[] | undefined>
const page = route.query.page as string | undefined;
const tags = route.query.tags as string[] | undefined;
</script>3.2 類型安全的 Query
typescript
interface SearchQuery {
keyword?: string;
page?: string;
sort?: 'asc' | 'desc';
}
function useSearchQuery(): SearchQuery {
const route = useRoute();
return {
keyword: route.query.keyword as string | undefined,
page: route.query.page as string | undefined,
sort: route.query.sort as 'asc' | 'desc' | undefined,
};
}四、 導航
4.1 useRouter
vue
<script setup lang="ts">
import { useRouter } from "vue-router";
const router = useRouter();
function goToUser(id: number) {
router.push({ name: "user", params: { id: String(id) } });
}
function goToSearch(keyword: string) {
router.push({ path: "/search", query: { keyword } });
}
function goBack() {
router.back();
}
</script>4.2 類型安全的導航
typescript
import type { RouteLocationRaw } from 'vue-router';
// 定義應用路由
type AppRoutes =
| { name: 'home' }
| { name: 'user'; params: { id: string } }
| { name: 'post'; params: { id: string }; query?: { preview?: string } };
function navigateTo(route: AppRoutes) {
const router = useRouter();
router.push(route as RouteLocationRaw);
}
// 使用
navigateTo({ name: 'user', params: { id: '123' } });五、 導航守衛
5.1 全域守衛
typescript
import type { NavigationGuardNext, RouteLocationNormalized } from 'vue-router';
router.beforeEach(
(
to: RouteLocationNormalized,
from: RouteLocationNormalized,
next: NavigationGuardNext
) => {
const isAuthenticated = checkAuth();
if (to.meta.requiresAuth && !isAuthenticated) {
next({ name: 'login' });
} else {
next();
}
}
);5.2 路由 Meta 類型
typescript
// 擴展 RouteMeta
declare module 'vue-router' {
interface RouteMeta {
requiresAuth?: boolean;
title?: string;
roles?: string[];
}
}
// 使用
const routes: RouteRecordRaw[] = [
{
path: '/dashboard',
name: 'dashboard',
component: Dashboard,
meta: {
requiresAuth: true,
title: 'Dashboard',
roles: ['admin', 'user'],
},
},
];
// 存取 meta
router.beforeEach((to) => {
document.title = to.meta.title ?? 'My App';
if (to.meta.requiresAuth) {
// 檢查認證
}
if (to.meta.roles) {
// 檢查角色
}
});5.3 組件內守衛
vue
<script setup lang="ts">
import { onBeforeRouteLeave, onBeforeRouteUpdate } from "vue-router";
import type { RouteLocationNormalized } from "vue-router";
onBeforeRouteLeave((to, from) => {
const answer = window.confirm("確定要離開嗎?");
if (!answer) return false;
});
onBeforeRouteUpdate((to: RouteLocationNormalized) => {
// 路由參數變化時
console.log("Route updated:", to.params);
});
</script>六、 巢狀路由
6.1 定義巢狀路由
typescript
const routes: RouteRecordRaw[] = [
{
path: '/user/:id',
name: 'user',
component: () => import('@/views/User.vue'),
children: [
{
path: '',
name: 'user-profile',
component: () => import('@/views/UserProfile.vue'),
},
{
path: 'posts',
name: 'user-posts',
component: () => import('@/views/UserPosts.vue'),
},
{
path: 'settings',
name: 'user-settings',
component: () => import('@/views/UserSettings.vue'),
meta: { requiresAuth: true },
},
],
},
];七、 路由組合函式
7.1 基本封裝
typescript
// composables/useRouteParams.ts
import { computed } from 'vue';
import { useRoute } from 'vue-router';
export function useRouteParams() {
const route = useRoute();
const id = computed(() => route.params.id as string);
const slug = computed(() => route.params.slug as string | undefined);
return { id, slug };
}7.2 完整範例
typescript
// composables/useRouteMeta.ts
import { computed } from 'vue';
import { useRoute } from 'vue-router';
export function useRouteMeta() {
const route = useRoute();
const title = computed(() => route.meta.title as string | undefined);
const requiresAuth = computed(() => route.meta.requiresAuth ?? false);
const roles = computed(() => route.meta.roles as string[] | undefined);
return { title, requiresAuth, roles };
}八、 實用範例
8.1 完整路由配置
typescript
// router/index.ts
import {
createRouter,
createWebHistory,
type RouteRecordRaw,
} from 'vue-router';
// 擴展 Meta
declare module 'vue-router' {
interface RouteMeta {
requiresAuth?: boolean;
title?: string;
layout?: 'default' | 'blank';
}
}
const routes: RouteRecordRaw[] = [
{
path: '/',
name: 'home',
component: () => import('@/views/Home.vue'),
meta: { title: 'Home', layout: 'default' },
},
{
path: '/login',
name: 'login',
component: () => import('@/views/Login.vue'),
meta: { title: 'Login', layout: 'blank' },
},
{
path: '/dashboard',
name: 'dashboard',
component: () => import('@/views/Dashboard.vue'),
meta: { title: 'Dashboard', requiresAuth: true },
},
{
path: '/:pathMatch(.*)*',
name: 'not-found',
component: () => import('@/views/NotFound.vue'),
meta: { title: '404' },
},
];
const router = createRouter({
history: createWebHistory(),
routes,
});
// 全域守衛
router.beforeEach((to, from, next) => {
document.title = to.meta.title ?? 'My App';
if (to.meta.requiresAuth && !isAuthenticated()) {
next({ name: 'login', query: { redirect: to.fullPath } });
} else {
next();
}
});
function isAuthenticated(): boolean {
return !!localStorage.getItem('token');
}
export default router;總結
| 類型 | 說明 |
|---|---|
RouteRecordRaw | 路由配置 |
RouteLocationNormalized | 標準化路由 |
RouteLocationRaw | 導航目標 |
NavigationGuardNext | next 函式 |
RouteMeta | 路由 meta |
> **推薦做法**:
- 擴展 RouteMeta 介面
- 封裝路由邏輯為組合函式
- 使用懶加載元件
進階挑戰
- 實作一個路由權限控制系統
- 建立動態路由配置(根據使用者角色)
- 實作路由切換動畫