路由管理
# 04.路由管理
# 目录介绍
# 4.1 路由基础
# 4.1.1 什么是前端路由
前端路由是单页应用(SPA)的核心技术,实现URL变化时不刷新页面,而是切换展示不同的组件。
传统多页应用(MPA):
/home → 请求服务器 → 返回 home.html → 整页刷新
/about → 请求服务器 → 返回 about.html → 整页刷新
单页应用(SPA):
/home → 前端路由匹配 → 渲染 Home 组件 → 不刷新页面
/about → 前端路由匹配 → 渲染 About 组件 → 不刷新页面
1
2
3
4
5
6
7
2
3
4
5
6
7
# 4.1.2 安装Vue Router
# 安装 Vue Router 4(适配 Vue 3)
npm install vue-router@4
1
2
2
# 4.1.3 配置路由表
// src/router/index.ts
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '@/views/HomeView.vue'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'home',
component: HomeView
},
{
path: '/about',
name: 'about',
// 路由懒加载:只有访问时才加载组件
component: () => import('@/views/AboutView.vue')
},
{
path: '/user/:id',
name: 'user',
component: () => import('@/views/UserView.vue')
}
]
})
export default router
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// src/main.ts 中注册路由
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(router) // 注册路由插件
app.mount('#app')
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
# 4.1.4 RouterView和RouterLink
<!-- src/App.vue -->
<template>
<nav>
<!-- RouterLink:声明式导航(生成 <a> 标签) -->
<RouterLink to="/">首页</RouterLink>
<RouterLink to="/about">关于</RouterLink>
<RouterLink :to="{ name: 'user', params: { id: 1 } }">用户</RouterLink>
</nav>
<!-- RouterView:路由出口,匹配的组件渲染在这里 -->
<RouterView />
</template>
<script setup lang="ts">
import { RouterLink, RouterView } from 'vue-router'
</script>
<style>
/* 激活状态的链接样式 */
.router-link-active {
color: #42b883;
font-weight: bold;
}
/* 精确匹配的激活样式 */
.router-link-exact-active {
border-bottom: 2px solid #42b883;
}
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 4.1.5 综合案例与思考
综合案例:带导航的基础路由应用
// router/index.ts
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(),
routes: [
{ path: '/', name: 'home', component: () => import('@/views/Home.vue') },
{ path: '/products', name: 'products', component: () => import('@/views/Products.vue') },
{ path: '/contact', name: 'contact', component: () => import('@/views/Contact.vue') }
]
})
export default router
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
<!-- App.vue -->
<template>
<div class="app">
<nav class="navbar">
<div class="brand">MyApp</div>
<div class="nav-links">
<RouterLink
v-for="link in navLinks"
:key="link.path"
:to="link.path"
class="nav-link"
>
{{ link.label }}
</RouterLink>
</div>
</nav>
<main class="content">
<RouterView />
</main>
</div>
</template>
<script setup lang="ts">
const navLinks = [
{ path: '/', label: '首页' },
{ path: '/products', label: '产品' },
{ path: '/contact', label: '联系' }
]
</script>
<style>
.navbar { display: flex; justify-content: space-between; padding: 16px 24px; background: #1a1a2e; }
.brand { color: white; font-size: 20px; font-weight: bold; }
.nav-link { color: #ccc; margin-left: 24px; text-decoration: none; }
.nav-link.router-link-active { color: #42b883; }
.content { padding: 24px; }
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
思考题:
- 前端路由和后端路由有什么本质区别?SPA的路由不请求服务器,页面内容从哪里来?
RouterLink和普通<a>标签有什么区别?为什么要用RouterLink?- 路由懒加载
() => import(...)的原理是什么?它能带来什么性能提升?
# 4.2 路由模式
# 4.2.1 Hash模式
URL 中带 # 号,通过 hashchange 事件监听变化:
import { createRouter, createWebHashHistory } from 'vue-router'
const router = createRouter({
history: createWebHashHistory(), // Hash 模式
routes: [...]
})
// URL 形式:http://example.com/#/about
// # 后面的内容不会发送到服务器
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# 4.2.2 History模式
使用 HTML5 History API,URL 更美观:
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(), // History 模式
routes: [...]
})
// URL 形式:http://example.com/about
// 需要服务器配置兜底(所有路径返回 index.html)
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
Nginx 配置示例:
location / {
try_files $uri $uri/ /index.html;
}
1
2
3
2
3
# 4.2.3 两种模式对比
| 对比项 | Hash模式 | History模式 |
|---|---|---|
| URL形式 | /#/about | /about |
| 服务器配置 | 不需要 | 需要配置兜底 |
| SEO | 差(搜索引擎不识别#后内容) | 好 |
| 兼容性 | 所有浏览器 | IE10+ |
| 美观度 | 差 | 好 |
| 推荐场景 | 后台管理系统、不需要SEO | 面向用户的网站 |
# 4.2.4 综合案例与思考
思考题:
- 为什么 Hash 模式不需要服务器配置?
#在 URL 中有什么特殊含义? - History 模式下刷新页面返回404,是因为什么?如何解决?
- 如果你的项目需要 SSR(服务端渲染),应该选择哪种路由模式?
# 4.3 动态路由
# 4.3.1 路由参数
使用 :param 定义动态路由参数:
// 路由配置
const routes = [
{ path: '/user/:id', name: 'user', component: UserView },
{ path: '/post/:category/:id', name: 'post', component: PostView }
]
1
2
3
4
5
2
3
4
5
<!-- UserView.vue -->
<template>
<h2>用户详情:{{ userId }}</h2>
</template>
<script setup lang="ts">
import { useRoute, watch } from 'vue-router'
const route = useRoute()
const userId = route.params.id // 获取路由参数
// 注意:同一组件的路由参数变化时,组件不会重新创建
// 需要用 watch 监听参数变化
watch(
() => route.params.id,
(newId) => {
console.log('用户ID变化:', newId)
// 重新请求数据
}
)
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 4.3.2 查询参数
使用 ?key=value 传递查询参数:
<template>
<!-- 传递查询参数 -->
<RouterLink :to="{ path: '/search', query: { keyword: 'vue', page: 1 } }">
搜索
</RouterLink>
<!-- 生成: /search?keyword=vue&page=1 -->
</template>
1
2
3
4
5
6
7
2
3
4
5
6
7
<!-- SearchView.vue -->
<script setup lang="ts">
import { useRoute } from 'vue-router'
const route = useRoute()
const keyword = route.query.keyword // 'vue'
const page = route.query.page // '1'(注意:query值都是字符串)
</script>
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
# 4.3.3 嵌套路由
通过 children 配置嵌套路由:
const routes = [
{
path: '/dashboard',
component: DashboardLayout,
children: [
{ path: '', name: 'dashboard', component: Overview }, // /dashboard
{ path: 'analytics', component: Analytics }, // /dashboard/analytics
{ path: 'settings', component: Settings } // /dashboard/settings
]
}
]
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
<!-- DashboardLayout.vue -->
<template>
<div class="dashboard">
<aside class="sidebar">
<RouterLink to="/dashboard">概览</RouterLink>
<RouterLink to="/dashboard/analytics">分析</RouterLink>
<RouterLink to="/dashboard/settings">设置</RouterLink>
</aside>
<main class="main">
<!-- 嵌套路由的出口 -->
<RouterView />
</main>
</div>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
# 4.3.4 命名路由和命名视图
// 命名路由:通过 name 导航
const routes = [
{ path: '/user/:id', name: 'user-detail', component: UserDetail }
]
// 使用命名路由
// <RouterLink :to="{ name: 'user-detail', params: { id: 123 } }">用户</RouterLink>
1
2
3
4
5
6
7
2
3
4
5
6
7
// 命名视图:一个页面多个 RouterView
const routes = [
{
path: '/admin',
components: { // 注意是 components(复数)
default: AdminMain,
sidebar: AdminSidebar,
header: AdminHeader
}
}
]
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
<template>
<RouterView name="header" />
<RouterView name="sidebar" />
<RouterView /> <!-- default -->
</template>
1
2
3
4
5
2
3
4
5
# 4.3.5 综合案例与思考
综合案例:博客文章系统路由
// router/index.ts
const routes = [
{ path: '/', name: 'home', component: () => import('@/views/Home.vue') },
{
path: '/blog',
component: () => import('@/views/BlogLayout.vue'),
children: [
{ path: '', name: 'blog-list', component: () => import('@/views/BlogList.vue') },
{ path: ':id', name: 'blog-detail', component: () => import('@/views/BlogDetail.vue') },
{ path: 'category/:category', name: 'blog-category', component: () => import('@/views/BlogCategory.vue') }
]
}
]
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
<!-- BlogDetail.vue -->
<template>
<article v-if="post">
<h1>{{ post.title }}</h1>
<p class="meta">分类: {{ post.category }} | ID: {{ postId }}</p>
<div v-html="post.content"></div>
<div class="nav">
<RouterLink v-if="prevId" :to="{ name: 'blog-detail', params: { id: prevId } }">
← 上一篇
</RouterLink>
<RouterLink v-if="nextId" :to="{ name: 'blog-detail', params: { id: nextId } }">
下一篇 →
</RouterLink>
</div>
</article>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue'
import { useRoute } from 'vue-router'
const route = useRoute()
const post = ref<any>(null)
const postId = ref('')
const prevId = ref('')
const nextId = ref('')
const fetchPost = async (id: string) => {
postId.value = id
// 模拟API请求
post.value = {
title: `文章 ${id}`,
category: '前端',
content: `<p>这是文章 ${id} 的内容...</p>`
}
prevId.value = Number(id) > 1 ? String(Number(id) - 1) : ''
nextId.value = String(Number(id) + 1)
}
// 监听路由参数变化
watch(() => route.params.id, (newId) => {
if (newId) fetchPost(newId as string)
}, { immediate: true })
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
思考题:
- 路由参数(params)和查询参数(query)有什么区别?各适用于什么场景?
- 嵌套路由中子路由的
path不加/开头和加/开头有什么区别? - 当路由参数变化但组件相同时(如从
/user/1到/user/2),Vue为什么不重新创建组件?
# 4.4 编程式导航
# 4.4.1 useRouter和useRoute
<script setup lang="ts">
import { useRouter, useRoute } from 'vue-router'
// useRouter:获取路由器实例,用于导航
const router = useRouter()
// useRoute:获取当前路由信息(响应式)
const route = useRoute()
console.log(route.path) // 当前路径
console.log(route.params) // 路由参数
console.log(route.query) // 查询参数
console.log(route.name) // 路由名称
console.log(route.meta) // 路由元信息
console.log(route.fullPath) // 完整路径(含query和hash)
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 4.4.2 push和replace
<script setup lang="ts">
import { useRouter } from 'vue-router'
const router = useRouter()
// push:跳转并添加历史记录
const goToAbout = () => {
router.push('/about')
}
// 使用对象形式(推荐)
const goToUser = (id: number) => {
router.push({ name: 'user', params: { id } })
}
// 带查询参数
const goToSearch = (keyword: string) => {
router.push({ path: '/search', query: { keyword, page: 1 } })
}
// replace:跳转但不添加历史记录(用户点击后退不会回到当前页)
const redirectToLogin = () => {
router.replace('/login')
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 4.4.3 前进后退
<script setup lang="ts">
import { useRouter } from 'vue-router'
const router = useRouter()
// 后退一步
const goBack = () => router.go(-1)
// 前进一步
const goForward = () => router.go(1)
// 后退两步
const goBack2 = () => router.go(-2)
// 简写
const back = () => router.back() // 等价于 router.go(-1)
const forward = () => router.forward() // 等价于 router.go(1)
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 4.4.4 综合案例与思考
综合案例:带面包屑的页面导航
<template>
<div>
<nav class="breadcrumb">
<span v-for="(crumb, i) in breadcrumbs" :key="i">
<a v-if="i < breadcrumbs.length - 1" @click="goTo(crumb.path)" href="#">
{{ crumb.label }}
</a>
<span v-else>{{ crumb.label }}</span>
<span v-if="i < breadcrumbs.length - 1"> / </span>
</span>
</nav>
<div class="actions">
<button @click="router.back()">← 返回</button>
<button @click="goHome">首页</button>
<button @click="goToUser(42)">用户42</button>
<button @click="search('Vue3')">搜索Vue3</button>
</div>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { useRouter, useRoute } from 'vue-router'
const router = useRouter()
const route = useRoute()
const breadcrumbs = computed(() => {
const crumbs = [{ label: '首页', path: '/' }]
const segments = route.path.split('/').filter(Boolean)
let path = ''
segments.forEach(segment => {
path += '/' + segment
crumbs.push({ label: segment, path })
})
return crumbs
})
const goTo = (path: string) => router.push(path)
const goHome = () => router.push('/')
const goToUser = (id: number) => router.push({ name: 'user', params: { id } })
const search = (keyword: string) => router.push({ path: '/search', query: { keyword } })
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
思考题:
router.push和router.replace的区别是什么?登录成功后应该用哪个跳转?useRoute()返回的是响应式对象,这意味着什么?为什么需要用watch监听它的变化?- 编程式导航和声明式导航(
RouterLink)各适用于什么场景?
# 4.5 导航守卫
# 4.5.1 全局前置守卫
在路由跳转前执行逻辑(如权限验证):
// router/index.ts
router.beforeEach((to, from) => {
const isAuthenticated = !!localStorage.getItem('token')
// 如果访问需要登录的页面,且未登录,重定向到登录页
if (to.meta.requiresAuth && !isAuthenticated) {
return { name: 'login', query: { redirect: to.fullPath } }
}
// 如果已登录,不允许再访问登录页
if (to.name === 'login' && isAuthenticated) {
return { name: 'home' }
}
// 返回 true 或 undefined 表示放行
})
// 全局后置守卫(不能改变导航,用于分析、修改标题等)
router.afterEach((to) => {
document.title = (to.meta.title as string) || '默认标题'
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 4.5.2 路由独享守卫
在路由配置中直接定义守卫:
const routes = [
{
path: '/admin',
component: AdminView,
beforeEnter: (to, from) => {
const userRole = getUserRole()
if (userRole !== 'admin') {
alert('权限不足')
return false // 取消导航
}
}
}
]
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
# 4.5.3 组件内守卫
在组件中使用导航守卫:
<script setup lang="ts">
import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'
// 离开当前路由前(如:表单未保存时提示)
onBeforeRouteLeave((to, from) => {
if (hasUnsavedChanges.value) {
const answer = confirm('有未保存的修改,确认离开吗?')
if (!answer) return false // 取消导航
}
})
// 当前路由参数变化时(如:/user/1 → /user/2)
onBeforeRouteUpdate((to, from) => {
console.log('路由参数变化:', from.params.id, '→', to.params.id)
})
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 4.5.4 完整导航流程
完整的导航解析流程:
1. 触发导航
2. ✓ 失活组件调用 onBeforeRouteLeave
3. ✓ 调用全局 beforeEach
4. ✓ 重用组件调用 onBeforeRouteUpdate
5. ✓ 路由配置中 beforeEnter
6. ✓ 解析异步路由组件
7. ✓ 激活组件中 beforeRouteEnter(Options API)
8. ✓ 调用全局 beforeResolve
9. ✓ 导航确认
10. ✓ 调用全局 afterEach
11. ✓ DOM 更新
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
# 4.5.5 综合案例与思考
综合案例:完整的路由权限系统
// router/index.ts
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(),
routes: [
{
path: '/login',
name: 'login',
component: () => import('@/views/Login.vue'),
meta: { title: '登录', guest: true }
},
{
path: '/',
name: 'home',
component: () => import('@/views/Home.vue'),
meta: { title: '首页' }
},
{
path: '/dashboard',
name: 'dashboard',
component: () => import('@/views/Dashboard.vue'),
meta: { title: '控制台', requiresAuth: true }
},
{
path: '/admin',
name: 'admin',
component: () => import('@/views/Admin.vue'),
meta: { title: '管理', requiresAuth: true, roles: ['admin'] }
}
]
})
// 全局前置守卫
router.beforeEach((to, from) => {
const token = localStorage.getItem('token')
const userRole = localStorage.getItem('role') || 'user'
// 需要登录但未登录
if (to.meta.requiresAuth && !token) {
return { name: 'login', query: { redirect: to.fullPath } }
}
// 已登录访问登录页
if (to.meta.guest && token) {
return { name: 'home' }
}
// 角色权限检查
const allowedRoles = to.meta.roles as string[] | undefined
if (allowedRoles && !allowedRoles.includes(userRole)) {
alert('权限不足')
return false
}
})
// 全局后置守卫:设置页面标题
router.afterEach((to) => {
document.title = `${to.meta.title || '应用'} - MyApp`
})
export default router
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
思考题:
- 全局守卫、路由独享守卫、组件内守卫各适用于什么场景?如何选择?
- 导航守卫中返回
false和返回{ name: 'login' }有什么区别? - 如何在导航守卫中实现"登录后跳回原页面"的功能?
# 4.6 路由进阶
# 4.6.1 路由懒加载
// 每个路由页面打包为独立的JS文件,按需加载
const routes = [
{
path: '/dashboard',
// 使用动态 import,Vite 自动进行代码分割
component: () => import('@/views/Dashboard.vue')
},
{
path: '/settings',
// 使用注释为chunk命名
component: () => import(/* webpackChunkName: "settings" */ '@/views/Settings.vue')
}
]
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
# 4.6.2 路由元信息meta
const routes = [
{
path: '/admin',
component: AdminView,
meta: {
requiresAuth: true, // 需要登录
roles: ['admin'], // 允许的角色
title: '管理后台', // 页面标题
keepAlive: true, // 是否缓存组件
transition: 'fade' // 转场动画
}
}
]
// 在组件或守卫中访问 meta
const route = useRoute()
console.log(route.meta.title)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 4.6.3 滚动行为
const router = createRouter({
history: createWebHistory(),
routes: [...],
scrollBehavior(to, from, savedPosition) {
// 浏览器后退/前进时恢复位置
if (savedPosition) {
return savedPosition
}
// 跳转到锚点
if (to.hash) {
return { el: to.hash, behavior: 'smooth' }
}
// 默认回到顶部
return { top: 0, behavior: 'smooth' }
}
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 4.6.4 404和重定向
const routes = [
// 重定向
{ path: '/home', redirect: '/' },
{ path: '/old-path', redirect: { name: 'new-route' } },
// 别名
{ path: '/user', component: User, alias: '/people' },
// 404 页面(匹配所有未定义的路由,必须放在最后)
{
path: '/:pathMatch(.*)*',
name: 'not-found',
component: () => import('@/views/NotFound.vue')
}
]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 4.6.5 综合案例与思考
综合案例:带过渡动画和缓存的路由
<!-- App.vue -->
<template>
<nav>
<RouterLink to="/">首页</RouterLink>
<RouterLink to="/list">列表</RouterLink>
<RouterLink to="/detail/1">详情</RouterLink>
</nav>
<RouterView v-slot="{ Component, route }">
<!-- 路由过渡动画 -->
<Transition :name="route.meta.transition as string || 'fade'" mode="out-in">
<!-- KeepAlive 缓存组件 -->
<KeepAlive :include="cachedViews">
<component :is="Component" :key="route.path" />
</KeepAlive>
</Transition>
</RouterView>
</template>
<script setup lang="ts">
import { ref } from 'vue'
// 需要缓存的组件名称列表
const cachedViews = ref(['ListView'])
</script>
<style>
.fade-enter-active, .fade-leave-active {
transition: opacity 0.3s ease;
}
.fade-enter-from, .fade-leave-to {
opacity: 0;
}
.slide-enter-active, .slide-leave-active {
transition: transform 0.3s ease;
}
.slide-enter-from {
transform: translateX(100%);
}
.slide-leave-to {
transform: translateX(-100%);
}
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
思考题:
- 路由懒加载的原理是什么?它是如何实现"按需加载"的?
KeepAlive缓存路由组件时,生命周期会有什么变化?onActivated和onDeactivated是什么?- 如何实现"列表页进入详情页用右滑动画,返回列表页用左滑动画"的效果?
上次更新: 2026/06/10, 11:13:41