路由器和路由
新建一个 Vue3 项目,创建一个简单的 App.vue:
<template>
<div class="app">
<h2 class="title">Vue路由测试</h2>
<!-- 导航区 -->
<div class="navigate">
<a href="#" class="active">首页</a>
<a href="#">新闻</a>
<a href="#">关于</a>
</div>
<!-- 展示区 -->
<div class="main-content">
这里展示内容
</div>
</div>
</template>
<script setup lang="ts">
</script>
<style scoped>
/* App */
.title {
text-align: center;
word-spacing: 5px;
margin: 30px 0;
height: 70px;
line-height: 70px;
background-image: linear-gradient(45deg, gray, white);
border-radius: 10px;
box-shadow: 0 0 2px;
font-size: 30px;
}
.navigate {
display: flex;
justify-content: space-around;
margin: 0 100px;
}
.navigate a {
display: block;
text-align: center;
width: 90px;
height: 40px;
line-height: 40px;
border-radius: 10px;
background-color: gray;
text-decoration: none;
color: white;
font-size: 18px;
letter-spacing: 5px;
}
.navigate a.active {
background-color: #64967E;
color: #ffc268;
font-weight: 900;
text-shadow: 0 0 1px black;
font-family: 微软雅黑;
}
.main-content {
margin: 0 auto;
margin-top: 30px;
border-radius: 10px;
width: 90%;
height: 400px;
border: 1px solid;
}
</style>
包含三部分:
-
标题
-
导航栏
-
内容展示区
最终希望实现的效果是点击按钮可以加载不同的内容,下面通过 vue 的路由器(Router)实现这一点。
首先安装 vue 的路由组件:
npm install vue-router
创建路由文件src/router/index.ts
:
import {createRouter,createWebHistory} from 'vue-router'
import Home from '@/components/Home.vue'
import News from '@/components/News.vue'
import About from '@/components/About.vue'
// 创建路由器
const router = createRouter({
history: createWebHistory(), // 路由器的工作模式
routes:[ // 路由规则
{
path:'/home',
component: Home
},
{
path: '/news',
component: News
},
{
path: '/about',
component: About
}
]
})
// 暴露路由器
export default router
在这个路由文件中通过createRouter
方法创建了一个路由器(router),这个路由器包含三条路由(route)规则。每个路由规则包含一个路径和一个组件。
在 main.ts
中用 app 加载路由:
import './assets/main.css'
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
const app = createApp(App)
// 使用路由器
app.use(router)
app.mount('#app')
现在路由环境已经配置好了,Vue 应用已经可以通过路由器知道什么路径对应什么组件。但是还缺少一步——应用不知道路由规则中的组件应该加载到页面的什么地方。
需要在 App.vue
中加载路由规则中组件的部位使用RouterView
进行占位:
<template>
<div class="app">
<h2 class="title">Vue路由测试</h2>
<!-- 导航区 -->
<div class="navigate">
<a href="#" class="active">首页</a>
<a href="#">新闻</a>
<a href="#">关于</a>
</div>
<!-- 展示区 -->
<div class="main-content">
<RouterView/>
</div>
</div>
</template>
<script setup lang="ts">
import {RouterView} from 'vue-router'
</script>
现在修改浏览器路径(比如 http://localhost:5173/about)已经可以看到相应组件的加载。
要让链接点击后跳转到相应的路由路径并加载组件,就不能使用a
标签,而是使用RouterLink
组件:
<template>
<div class="app">
<h2 class="title">Vue路由测试</h2>
<!-- 导航区 -->
<div class="navigate">
<RouterLink to="/home" class="active">首页</RouterLink>
<RouterLink to="/news">新闻</RouterLink>
<RouterLink to="/about">关于</RouterLink>
</div>
<!-- 展示区 -->
<div class="main-content">
<RouterView/>
</div>
</div>
</template>
<script setup lang="ts">
import {RouterView, RouterLink} from 'vue-router'
</script>
现在点击链接已经可以实现路由功能了,缺点是因为class="active"
的缘故第一个按钮一直高亮,无法实现点击哪个按钮哪个高亮。
可以使用RouterLink
的active-class
属性:
<div class="navigate">
<RouterLink to="/home" active-class="active">首页</RouterLink>
<RouterLink to="/news" active-class="active">新闻</RouterLink>
<RouterLink to="/about" active-class="active">关于</RouterLink>
</div>
由路由控制和加载的组件被称作视图组件,通常会放在/src/views
或/src/pages
目录下。
此外,发生路由切换时,之前的组件会被从组件树卸载,这点可以通过使用生命周期钩子证明:
<script setup lang="ts" name="About">
import { onMounted,onUnmounted } from 'vue';
onMounted(()=>{
console.log("About 组件被加载")
})
onUnmounted(()=>{
console.log("About 组件被卸载")
})
</script>
路由器工作模式
前端框架的路由器通常有两种工作模式:
-
History
-
Hash
vue3 使用 History 模式:
import {createRouter,createWebHistory} from 'vue-router'
// ...
// 创建路由器
const router = createRouter({
history: createWebHistory(), // 路由器的工作模式
// ...
})
// 暴露路由器
export default router
此时浏览器中的请求路径没有#
,比如 http://localhost:5173/home,更接近于传统 web 应用的请求路径。
使用 Hash 模式:
import {createRouter, createWebHashHistory} from 'vue-router'
// ...
// 创建路由器
const router = createRouter({
history: createWebHashHistory(), // 路由器的工作模式
// ...
})
// 暴露路由器
export default router
此时浏览器中的请求路径中会有#
符号,比如:http://localhost:5173/home#/。
这两种模式各有优缺点:
模式 | 优点 | 缺点 |
---|---|---|
History | 没有#,SEO良好 | 上线部署时需要修改 Nginx 设置 |
Hash | 上线部署时不需要特殊设置 | 有#、不利于 SEO |
通常来说,面向 2C 的网站会使用 History 模式,面向 2B 的后台管理系统会使用 Hash 模式。
to 属性
RouterLink
的to
属性除了可以直接指定路径进行跳转:
<RouterLink to="/home" active-class="active">首页</RouterLink>
还可以指定包含路径的对象:
<RouterLink :to="{path:'/news'}" active-class="active">新闻</RouterLink>
注意属性值是表达式时属性名称前要使用
:
路由是可以命名的:
const router = createRouter({
history: createWebHistory(), // 路由器的工作模式
routes:[ // 路由规则
{
name:'zhuye',
path:'/home',
component: Home
},
{
name:'xinwen',
path: '/news',
component: News
},
{
name: 'guanyu',
path: '/about',
component: About
}
]
})
此时 to
属性还可以通过路由名称进行跳转:
<RouterLink :to="{name:'guanyu'}" active-class="active">关于</RouterLink>
嵌套路由
首先将新闻页面的新闻列表改为动态加载:
<template>
<div class="news">
<ul>
<li v-for="news in newsList">
<RouterLink to="#"></RouterLink>
</li>
</ul>
</div>
</template>
<script setup lang="ts" name="News">
import { ref } from 'vue'
import { RouterLink } from 'vue-router'
interface News {
id: number
title: string
content: string
}
const newsList = ref<News[]>([
{ id: 1, title: '头条新闻', content: '以色列轰炸卡塔尔' },
{ id: 2, title: '科技新闻', content: '网传Windows更新补丁损坏硬盘' },
{ id: 3, title: '震惊', content: '江南皮革厂倒闭了' }
])
</script>
为新闻页面增加一个新闻详情页作为子展示区:
<template>
<ul class="news-list">
<li>编号:xxx</li>
<li>标题:xxx</li>
<li>内容:xxx</li>
</ul>
</template>
<script setup lang="ts" name="About">
</script>
<style scoped>
.news-list {
list-style: none;
padding-left: 20px;
}
.news-list>li {
line-height: 30px;
}
</style>
在路由规则中添加一个子路由,指向新闻详情页的组件:
// ...
import Detail from '@/views/Detail.vue'
// 创建路由器
const router = createRouter({
history: createWebHistory(), // 路由器的工作模式
routes:[ // 路由规则
{
name:'zhuye',
path:'/home',
component: Home
},
{
name:'xinwen',
path: '/news',
component: News,
children:[
{
path: 'detail',
component: Detail
}
]
},
{
name: 'guanyu',
path: '/about',
component: About
}
]
})
// 暴露路由器
export default router
注意,子路由的path
属性不需要以/
开头。
在新闻页增加一个RouterView
占位:
<template>
<div class="news">
<ul>
<li v-for="news in newsList">
<RouterLink to="/news/detail"></RouterLink>
</li>
</ul>
<div class="news-content">
<RouterView></RouterView>
</div>
</div>
</template>
路由传参
query
可以通过 to
属性以拼接查询字符串的方式传递参数:
<li v-for="news in newsList">
<RouterLink :to="`/news/detail?id=${news.id}&title=${news.title}&content=${news.content}`">{{ news.title }}</RouterLink>
</li>
在 Detail.vue 中接收参数:
<template>
<ul class="news-list">
<li>编号:</li>
<li>标题:</li>
<li>内容:</li>
</ul>
</template>
<script setup lang="ts" name="About">
import {useRoute} from 'vue-router'
const route = useRoute()
</script>
通过useRoute
(这是一个 hooks)获取到route
,其query
属性中有路由中传递的参数。
除了拼接字符串,可以用对象的方式更容易的传递 query 参数:
<li v-for="news in newsList">
<RouterLink :to="{path:'/news/detail',query:news}">{{ news.title }}</RouterLink>
</li>
在读取路由参数时可能会通过解构赋值进行简化:
<template>
<ul class="news-list">
<li>编号:</li>
<li>标题:</li>
<li>内容:</li>
</ul>
</template>
<script setup lang="ts" name="About">
import {useRoute} from 'vue-router'
const route = useRoute()
const {query} = route
</script>
但需要注意的是这样写是有问题的,因为route
是一个响应式数据,但从中解构出的query
是一个非响应式数据,在这个示例中,多个新闻链接都使用的是同一个组件Detail.vue
,因此进行路由切换不会触发组件的卸载和加载,继而其内容不会发生变化。
此时需要借助toRefs
方法解构出响应式数据:
<script setup lang="ts" name="About">
import {useRoute} from 'vue-router'
import { toRefs } from 'vue'
const route = useRoute()
const {query} = toRefs(route)
</script>
params
路由的 params 传参有点像后端框架定义的路径参数:
{
name:'xinwen',
path: '/news',
component: News,
children:[
{
path: 'detail/:id/:title/:content',
component: Detail
}
]
},
这里使用:xxx
的方式在子路由的路径中为 params 参数占位。
传参:
<li v-for="news in newsList">
<RouterLink :to="`/news/detail/${news.id}/${news.title}/${news.content}`">{{ news.title }}</RouterLink>
</li>
接收参数:
<template>
<ul class="news-list">
<li>编号:</li>
<li>标题:</li>
<li>内容:</li>
</ul>
</template>
<script setup lang="ts" name="About">
import {useRoute} from 'vue-router'
import { toRefs } from 'vue'
const route = useRoute()
const {params} = toRefs(route)
</script>
与接收query
参数类似,区别是要从route.params
属性获取参数。
params 参数也可以用对象的方式传递:
<li v-for="news in newsList">
<RouterLink :to="{name:'xiangqing',params: news}">{{ news.title }}</RouterLink>
</li>
需要注意的是,这种方式下必须使用路由名称(name
)而非路径(path
)指定路由,否则会报错。
最后,如果 params 参数的最后一位是可有可无的,可以:
path: 'detail/:id/:title/:content?',
路由 props 配置
路由使用 params 传参时,可以添加一个props:true
属性:
{
name: 'xiangqing',
path: 'detail/:id/:title/:content?',
component: Detail,
props: true
}
此时路由会将 params 参数作为路由组件的 props 进行传递,换言之,可以在路由组件中使用接收 props 的方式接收 params 参数:
<template>
<ul class="news-list">
<li>编号:</li>
<li>标题:</li>
<li>内容:</li>
</ul>
</template>
<script setup lang="ts" name="About">
defineProps(['id','title','content'])
</script>
如果是 query 传参,就需要以另一种方式实现作为组件的 props 传递:
{
name: 'xiangqing',
path: 'detail',
component: Detail,
props(route){
return route.query
}
}
此时路由的props
属性是一个方法,其接收的参数实际上就是route
对象,返回值是一个对象,其属性会作为路由组件的 props 进行传递。在这里使用了route.query
以将路由的query
参数作为路由组件的 props 进行传递,实际上你可以通过这种方式向路由组件传递任何想传递的内容。
最后还有一种形式:
props:{
id: '你好',
title: '哈哈',
content: '嘿嘿'
}
直接将路由的 props 设置成一个对象。当然在这里没有任何意义,相当于写死了新闻内容,任何新闻链接都展示一样的内容。着相当于第二种方式的特异化,即没有使用参数直接返回一个特定内容的对象。
replace 属性
实现路由跳转后,会修改浏览器的历史记录,有两种模式:push 和 replace,默认是 push。
push 实际上就是将当前请求路径加入历史记录的栈顶,此时可以通过浏览器的后退操作退回到前一个页面(请求地址)。replace 则是用当前请求路径替换掉前一个路径在历史记录栈中的位置,这样就无法会退到之前的页面(请求地址)。
要让 RouterLink 标签使用 replace 跳转,只需要添加一个replace
属性:
<RouterLink replace :to="{name:'guanyu'}" active-class="active">关于</RouterLink>
此时如果通过这个 RouterLink 跳转,浏览器执行回退操作会退回到上上一个页面。
编程式路由跳转
除了使用 RouterLink 组件实现路由跳转,还可以用编程的方式实现路由跳转:
<template>
<div class="news">
<ul>
<li v-for="news in newsList">
<button @click="showNewsDetail(news)">新闻详情</button>
<RouterLink :to="{ name: 'xiangqing', query: news }"></RouterLink>
</li>
</ul>
<div class="news-content">
<RouterView></RouterView>
</div>
</div>
</template>
<script setup lang="ts" name="News">
import { ref } from 'vue'
import { RouterLink, useRouter } from 'vue-router'
interface News {
id: number
title: string
content: string
}
const newsList = ref<News[]>([
{ id: 1, title: '头条新闻', content: '以色列轰炸卡塔尔' },
{ id: 2, title: '科技新闻', content: '网传Windows更新补丁损坏硬盘' },
{ id: 3, title: '震惊', content: '江南皮革厂倒闭了' }
])
const router = useRouter()
const showNewsDetail = (news:News) => {
router.push(`/news/detail?id=${news.id}&title=${news.title}&content=${news.content}`)
}
</script>
通过useRouter
方法获取到路由器对象router
,并且通过router.push
在按钮点击事件中实现了路由跳转。
useRouter
是获取路由器,useRoute
是获取当前路由,注意有没有r
的区别,这里很容易搞混。
router.push
能接收的参数与RouterLink
的to
属性一致,因此除了普通的字符串形式的路径外,还可以是对象:
const showNewsDetail = (news: News) => {
router.push({
path: '/news/detail',
query: { news }
})
}
router 也可以以 replace 的方式跳转:
router.replace(...)
重定向
当前路由设置中没有对根路径/
的设置,因此访问项目根路径时不会加载任何路由组件,页面是空白的。要让根路径能展示有效的内容页,最简单的方式是为其设置一个路由重定向:
const router = createRouter({
history: createWebHistory(), // 路由器的工作模式
routes:[ // 路由规则
// ...
{
path:'/',
redirect: '/home'
}
]
})
现在只要访问根路径,比如http://localhost:5173/
,就会自动跳转到/home
路径。
文章评论