Appearance
基础路由 vue-router@4.2.5
Vue Router 是 Vue.js 的官方路由。它与 Vue.js 核心深度集成,让用 Vue.js 构建单页应用变得轻而易举。功能包括:
- 嵌套路由映射
- 动态路由选择
- 模块化、基于组件的路由配置
- 路由参数、查询、通配符
- 展示由 Vue.js 的过渡系统提供的过渡效果
- 细致的导航控制
- 自动激活 CSS 类的链接
- HTML5 history 模式或 hash 模式
- 可定制的滚动行为
- URL 的正确编码
两个注意点
- 路由组件通常存放在
pages
或者views
文件夹,一般组件通常存放在components
文件夹 - 通过点击导航, 视觉效果上"消失"了的路由组件,默认是被销毁掉的, 需要的时候再去挂载
1. 入门
如果项目中还没有安装vue-router
需要使用包管理器安装一下:
bash
pnpm add vue-router
TIP
VUE3 使用的路由版本是 vue-router@4, 安装时使用默认最新版即可
router-link: 使得 Vue Router 可以在不重新加载页面的情况下更改 URL,处理 URL 的生成以及编码。
router-view: 显示与 URL 对应的组件。你可以把它放在任何地方,以适应你的布局。
html
<script setup lang="ts"></script>
<template>
<div class="app">
<h2 class="title">Vue路由测试</h2>
<!-- 导航区 -->
<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>
<!-- 展示区 -->
<div class="main-content">
<RouterView></RouterView>
</div>
</div>
</template>
TIP
别忘记在main.ts
中,引入路由并使用
ts
import './assets/main.css'
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
createApp(App).use(router).mount('#app')
路由传参有 2 中方式,分别是query
和params
,params 是路由的一部分,而 query 不会改变路由的路径,它通常用于查询字符串的参数传递。
1.1 命名路由
命名路由是 vue-router 中的一种特性,它允许你给路由规则指定一个名字,使得在应用程序中通过名称来标识和导航到特定的路由,而不是直接使用路径。
TIP
使用命名路由的好处是,当你需要改变路径时,你只需要改变路由规则中的路径,而不需要改动所有使用该路由的地方。这样可以提高代码的可维护性。
ts
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(),
routes: [
// ...其他路由规则
{
path: '/news',
component: () => import('@/pages/News.vue'),
children: [
{
name: 'xiangqing',
path: 'detail/:id/:title/:content',
component: () => import('@/pages/Detail.vue'),
},
],
},
],
})
html
<div class="news">
<ul>
<li v-for="news in newsList" :key="news.id">
<RouterLink
:to="{
name: 'xiangqing',
params: { id: news.id, title: news.title, content: news.content },
}">
{{ news.title }}
</RouterLink>
</li>
</ul>
<div class="news-content">
<RouterView></RouterView>
</div>
</div>
命名路由是 vue-router 中提高路由管理灵活性和可维护性的重要特性之一。
1.2 嵌套路由
嵌套路由是一种常见的特性,它允许你在一个路由组件内部定义更多的路由规则。这对于构建具有层级结构的用户界面非常有用,比如后台管理系统的左侧菜单和主内容区域,或者前端页面的页头、侧边栏和主内容区域等。
要使用嵌套路由,你需要在父路由组件中定义一个 <router-view>
,这是子路由组件将被渲染的地方。然后,你可以在路由配置中为父路由定义一个 children 数组,数组中包含嵌套的路由规则。
下面是一个嵌套路由的基本示例:
ts
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(),
routes: [
// ...其他路由规则
{
path: '/news',
component: () => import('@/pages/News.vue'),
children: [
{
name: 'xiangqing', // 注意这里没有前斜杠,它是一个相对路径
path: 'detail/:id/:title/:content',
component: () => import('@/pages/Detail.vue'),
},
],
},
],
})
html
<div class="news">
<ul>
<li v-for="news in newsList" :key="news.id">
<RouterLink
:to="{
name: 'xiangqing',
params: { id: news.id, title: news.title, content: news.content },
}">
{{ news.title }}
</RouterLink>
</li>
</ul>
<div class="news-content">
<RouterView></RouterView>
</div>
</div>
嵌套路由的使用非常灵活,你可以在任何级别上嵌套路由,以构建复杂的页面结构。这在构建大型应用程序时非常有用,可以帮助你更好地组织代码和界面。
1.3 query 传参
query
传参通常用于搜索、排序等操作,这些参数不是路由的一部分,不会影响路由的匹配。它们以键值对的形式出现在 URL 的查询字符串中,例如 ?foo=bar&baz=qux
。
html
<template>
<div class="news">
<ul>
<li v-for="news in newsList" :key="news.id">
<!-- 第一种写法 不易维护-->
<!-- <RouterLink
:to="`/news/detail?id=${news.id}&title=${news.title}&content=${news.content}`">
{{ news.title }}
</RouterLink> -->
<!-- 第二种写法 对象写法-->
<RouterLink
:to="{
path: '/news/detail',
query: { id: news.id, title: news.title, content: news.content },
}">
{{ news.title }}
</RouterLink>
</li>
</ul>
<div class="news-content">
<RouterView></RouterView>
</div>
</div>
</template>
<script setup lang="ts" name="News">
import { reactive } from 'vue'
const newsList = reactive([
{ id: 1, title: '新闻001', content: '新闻001的内容' },
{ id: 2, title: '新闻002', content: '新闻002的内容' },
{ id: 3, title: '新闻003', content: '新闻003的内容' },
{ id: 4, title: '新闻004', content: '新闻004的内容' },
{ id: 5, title: '新闻005', content: '新闻005的内容' },
{ id: 6, title: '新闻006', content: '新闻006的内容' },
])
</script>
html
<template>
<ul class="news-list">
<li>编号:{{ query.id }}</li>
<li>标题:{{ query.title }}</li>
<li>内容:{{ query.content }}</li>
</ul>
</template>
<script setup lang="ts" name="Detail">
import { useRoute } from 'vue-router'
import { toRef } from 'vue'
const route = useRoute()
const query = toRef(route, 'query')
</script>
ts
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(),
routes: [
// ...省略无关路由
{
path: '/news',
component: () => import('@/pages/News.vue'),
children: [
{
name: 'xiangqing',
path: 'detail',
component: () => import('@/pages/Detail.vue'),
},
],
},
],
})
export default router
使用 query 传参的好处是它们不会改变路由的路径,因此不会触发路由的重新匹配。这对于不改变页面视图但需要改变数据的情况非常有用。例如,你可以使用 query 参数来保持用户在分页、筛选或排序操作中的位置。
1.4 params 传参
params
是路由的一部分,通常用于将必要的信息传递给路由组件,这些信息是动态的,并且会影响路由的匹配。params 会被包含在路由路径中,它们不会出现在 URL 的查询字符串中,而是直接嵌入到路由路径本身。
在定义路由时,你可以使用冒号 : 来标记路由路径中的动态部分,这些部分将成为 params:
html
<template>
<div class="news">
<ul>
<li v-for="news in newsList" :key="news.id">
<!-- 第一种写法 -->
<!-- <RouterLink
:to="`/news/detail/${news.id}/${news.title}/${news.content}`">
{{ news.title }}
</RouterLink> -->
<!-- 第二种写法 -->
<RouterLink
:to="{
name: 'xiangqing',
params: { id: news.id, title: news.title, content: news.content },
}">
{{ news.title }}
</RouterLink>
</li>
</ul>
<div class="news-content">
<RouterView></RouterView>
</div>
</div>
</template>
<script setup lang="ts" name="News">
import { reactive } from 'vue'
const newsList = reactive([
{ id: 1, title: '新闻001', content: '新闻001的内容' },
{ id: 2, title: '新闻002', content: '新闻002的内容' },
{ id: 3, title: '新闻003', content: '新闻003的内容' },
{ id: 4, title: '新闻004', content: '新闻004的内容' },
{ id: 5, title: '新闻005', content: '新闻005的内容' },
{ id: 6, title: '新闻006', content: '新闻006的内容' },
])
</script>
html
<template>
<ul class="news-list">
<li>编号:{{ params.id }}</li>
<li>标题:{{ params.title }}</li>
<li>内容:{{ params.content }}</li>
</ul>
</template>
<script setup lang="ts" name="Detail">
import { toRefs } from 'vue'
import { useRoute } from 'vue-router'
const route = useRoute()
const { params } = toRefs(route)
</script>
ts
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(),
routes: [
// ...省略无关路由
{
path: '/news',
component: () => import('@/pages/News.vue'),
children: [
{
name: 'xiangqing',
path: 'detail/:id/:title/:content',
component: () => import('@/pages/Detail.vue'),
},
],
},
],
})
export default router
注意事项:
- params 是必须的,如果你没有提供必要的 params,路由将无法匹配成功。
- 由于 params 是路由的一部分,所以改变 params 会导致路由的跳转和组件的重新渲染。
- params 不会出现在 URL 的查询字符串中,而是直接嵌入到路径中。
WARNING
传递params
参数时, 若使用to
的对象写法, 必须使用name
配置项,不能使用path
传递params
参数时,需要提前在规则中占位
1.5 路由的 props 参数
路由的 props 选项是一种将路由参数传递给组件的方式。这允许你在组件内部直接访问路由参数,而不必通过 $route 对象。这可以使得组件与路由解耦,使得组件更加可复用和易于测试。
有三种方式可以将路由 props 参数传递给组件:
- 布尔模式: 当
props
设置为true
时,路由的params参数
会被直接作为组件的 props 传递。该模式仅能传递 params 参数
html
<template>
<div class="news">
<ul>
<li v-for="news in newsList" :key="news.id">
<RouterLink
:to="{
name: 'xiangqing',
params: { id: news.id, title: news.title, content: news.content },
}">
{{ news.title }}
</RouterLink>
</li>
</ul>
<div class="news-content">
<RouterView></RouterView>
</div>
</div>
</template>
ts
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(),
routes: [
// ...省略其他路由
{
path: '/news',
component: () => import('@/pages/News.vue'),
children: [
{
name: 'xiangqing',
path: 'detail/:id/:title/:content',
component: () => import('@/pages/Detail.vue'),
// 第一种写法,将路由收到的所有params参数进行传递
props: true,
},
],
},
],
})
export default router
html
<template>
<ul class="news-list">
<li>编号:{{ id }}</li>
<li>标题:{{ title }}</li>
<li>内容:{{ content }}</li>
</ul>
</template>
<script setup lang="ts" name="Detail">
import { toRef } from 'vue'
import { useRoute } from 'vue-router'
const route = useRoute()
// 也可以用toRefs替代toRef, 注意写法的区别
const params = toRef(route, 'params')
defineProps(['id', 'title', 'content'])
</script>
- 对象模式(几乎不使用): 当 props 是一个对象时,它将把这个对象的所有属性作为 prop 传递给组件。
ts
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(),
routes: [
// ...省略其他路由
{
path: '/news',
component: () => import('@/pages/News.vue'),
children: [
{
name: 'xiangqing',
path: 'detail/:id/:title/:content',
component: () => import('@/pages/Detail.vue'),
// 第二种写法,直接传递静态数据
props: { a: '100', b: '200', c: '300' },
},
],
},
],
})
export default router
html
<template>
<ul class="news-list">
<li>编号:{{ a }}</li>
<li>标题:{{ b }}</li>
<li>内容:{{ c }}</li>
</ul>
</template>
<script setup lang="ts" name="Detail">
defineProps(['a', 'b', 'c'])
</script>
无论实际路由参数是什么,Detail 组件都将接收一个指定对象的 props。
- 函数模式: 当 props 是一个函数时,它允许你将路由参数转换为其他值,或者基于路由参数动态返回一个对象。
html
<template>
<div class="news">
<ul>
<li v-for="news in newsList" :key="news.id">
<RouterLink
:to="{
name: 'xiangqing',
query: { id: news.id, title: news.title, content: news.content },
}">
{{ news.title }}
</RouterLink>
</li>
</ul>
<div class="news-content">
<RouterView></RouterView>
</div>
</div>
</template>
ts
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(),
routes: [
// ...省略其他路由
{
path: '/news',
component: () => import('@/pages/News.vue'),
children: [
{
name: 'xiangqing',
path: 'detail',
component: () => import('@/pages/Detail.vue'),
// 第三种写法,可以自己决定传递什么参数
props: route => route.query || {},
},
],
},
],
})
export default router
当你想用 props 传递 query 参数的时候 你可以返回route.query
使用 props 选项可以让组件更加独立于路由,这在创建可复用的组件时非常有用,因为你可以在不同的路由中使用相同的组件,而不需要担心组件如何获取其数据。
2. 路由的两种工作模式
如果你预计用户主要使用现代浏览器,推荐使用 history 模式
,因为它提供了更美观和标准的 URL。譬如在线商城类项目
如果你的应用不需要 SEO,或者你的应用需要兼容旧版浏览器,或者你不想处理服务器端的配置,那么 hash 模式
可能更适合。譬如后台管理类项目
2.1 Hash 模式
特点:
- URL 中会包含 # 符号,例如 http://example.com/#/about。
- 通过 hashchange 事件监听 URL 的变化。
- 不需要服务器配置,因为 # 后的内容不会发送到服务器。
- 对于老版本的浏览器有更好的兼容性。
ts
import { createRouter, createWebHashHistory } from 'vue-router'
const router = createRouter({
history: createWebHashHistory(), // 使用 hash 模式
routes: [
// 路由配置...
],
})
2.2 History 模式
特点:
- URL 不含 # 符号,看起来更像是常规的 URL,例如 http://example.com/about。
- 通过 popstate 事件监听 URL 的变化。
- 需要服务器进行额外的配置,因为直接访问某个路由时,服务器需要能够处理所有可能的 URL,如果没有对应的资- 源,服务器应该返回同一个页面,通常这个页面就是你的 index.html。
- 对搜索引擎优化 (SEO) 更友好。
ts
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(), // 使用 history 模式
routes: [
// 路由配置...
],
})
附后端需要配置的操作:
nginx
location / {
try_files $uri $uri/ /index.html;
}
apache
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.html$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.html [L]
</IfModule>
js
const history = require('connect-history-api-fallback')
const express = require('express')
const app = express()
app.use(history())
app.use(express.static('public'))
app.listen(3000)
text
对于 GitHub Pages,你可以在仓库的根目录下添加一个名为 404.html 的文件,
其内容是你的入口页面 index.html 的副本。
这样,当 GitHub Pages 无法找到请求的页面时,它会返回 404.html,
从而允许 Vue Router 正常工作。
3. 路由的 replace 属性
在 Vue 应用中使用 replace 选项进行路由导航时,新的路由会替换掉当前的历史记录,而不是添加一个新的记录。
html
<router-link :to="{ name: 'home' }" replace>Home</router-link>
在这个例子中,点击这个链接将使用 replace 方法导航到 home 路由,而不会添加一个新的历史记录。
使用 replace 可以在某些情况下提供更好的用户体验,例如,当从登录页面重定向到主页时,你可能不希望用户能够通过后退按钮返回到登录页面。