Vue Router ——路由基础详解(一)
官网:入门 | Vue Router
一、简介
Vue Router 是 Vue.js 的官方路由。
客户端路由的作用是在单页应用 (SPA) 中将浏览器的 URL 和用户看到的内容绑定起来。当用户在应用中浏览不同页面时,URL 会随之更新,但页面不需要从服务器重新加载。
Vue Router 基于 Vue 的组件系统构建,你可以通过配置路由来告诉 Vue Router 为每个 URL 路径显示哪些组件。
二、安装
npm install vue-router@4
三、配置(通常全局配置)
1、App.vue
<template><h1>Hello App!</h1><p><strong>Current route path:</strong> {{ $route.fullPath }}</p><main><RouterView /></main>
</template>
App.vue 是 Vue 应用的根组件,也是整个项目的入口文件。
所有页面和组件都基于它进行切换和渲染。通过 main.js
将其挂载到 DOM 中(通常是 index.html
中的 #app
元素),从而启动整个应用
2、创建路由示例-router.js
路由器实例是通过调用 createRouter()
函数创建的:
import { createMemoryHistory, createRouter } from 'vue-router'const routes = [{ path: '/', redirect: '/one'},{ path: '/one', component: () => import('/src/components/One.vue') },
]const router = createRouter({history: createMemoryHistory(),routes,
})
export default router
这里的 routes
选项定义了一组路由,把 URL 路径映射到组件。
由 component
参数指定的组件就是先前在 App.vue
中被 <RouterView>
渲染的组件。这些路由组件通常被称为视图,但本质上它们只是普通的 Vue 组件。
这里的 history
选项控制了路由和 URL 路径是如何双向映射的。
3、注册路由器插件-main.js
一旦创建了我们的路由器实例,我们就需要将其注册为插件,这一步骤可以通过调用 use()
来完成。
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from "./router/router.js";createApp(App).use(router).mount('#app')
和大多数的 Vue 插件一样,use()
需要在 mount()
之前调用。
它的职责包括:
- 全局注册
RouterView
和RouterLink
组件。- 添加全局
$router
和$route
属性。- 启用
useRouter()
和useRoute()
组合式函数。- 触发路由器解析初始路由。
四、访问路由器和当前路由
//组合式
<script setup>
import { useRoute, useRouter } from 'vue-router'const router = useRouter()
const route = useRoute()
const toHellow = () => {//注意:没有this对象了console.log('当前页面路由', route.path)//跳转router.push('/HelloWorld')}
</script>
这里调用了 push()
,这是用于编程式导航的方法。
五、<RouterView>
和< RouterLink>
组件 RouterView
和 RouterLink
都是全局注册的,因此它们不需要在组件模板中导入。
但你也可以通过局部导入它们,例如 import { RouterLink } from 'vue-router'
。
<template><h1>Hello App!</h1><p><strong>Current route path:</strong> {{ $route.fullPath }}</p><nav><RouterLink to="/">Go to Home</RouterLink><RouterLink to="/about">Go to About</RouterLink></nav><main><RouterView /></main>
</template>
在这个
template
中使用了两个由 Vue Router 提供的组件:RouterLink
和RouterView
。
RouterLink:
不同于常规的<a>
标签,我们使用组件RouterLink
来创建链接。这使得 Vue Router 能够在不重新加载页面的情况下改变 URL,处理 URL 的生成、编码和其他功能。
RouterView
:可以使 Vue Router 知道你想要在哪里渲染当前 URL 路径对应的路由组件。它不一定要在App.vue
中,你可以把它放在任何地方,但它需要在某处被导入,否则 Vue Router 就不会渲染任何东西。
在模板中,组件的名字可以是 PascalCase 风格或 kebab-case 风格的。Vue 的模板编译器支持两种格式,因此 <RouterView>
和 <router-view>
通常是等效的。此时应该遵循你自己项目中使用的约定。
六、带参数的动态路由匹配
(适用于不同用户进入同一页面展示不同内容)
很多时候,我们需要将给定匹配模式的路由映射到同一个组件。
例如,我们可能有一个 User
组件,它应该对所有用户进行渲染,但用户 ID 不同。
在 Vue Router 中,我们可以在路径中使用一个动态字段来实现,我们称之为 路径参数 :
import User from './User.vue'// 这些都会传递给 `createRouter`const routes = [// 动态字段以冒号开始{ path: '/users/:id', component: User },]
/users/01
和 /users/02
这样的 URL 都会映射到同一个路由。
路径参数 用冒号 :
表示。当一个路由被匹配时,它的 params 的值将在每个组件中以 route.params
的形式暴露出来。因此,我们可以通过更新 User
的模板来呈现当前的用户 ID:
const route = useRoute(); // 使用useRoute()获取$route对象
const userId = ref(route.params.id); // 使用ref将$route.params.id包装成响应式数据
// 监听$route对象的变化,更新doorId的值watchEffect(() => {userId.value = route.params.id;});
1、响应路由变化
使用带有参数的路由时需要注意的是,当用户从 /users/johnny
导航到 /users/jolyne
时,相同的组件实例将被重复使用。因为两个路由都渲染同个组件,比起销毁再创建,复用则显得更加高效。不过,这也意味着组件的生命周期钩子不会被调用。
要对同一个组件中参数的变化做出响应的话,你可以简单地 watch $route
对象上的任意属性,在这个场景中,就是 $route.params
:
<script setup>
import { watch } from 'vue'
import { useRoute } from 'vue-router'const route = useRoute()watch(() => route.params.id, (newId, oldId) => {// 对路由变化做出响应...
})
</script>
或者,使用 beforeRouteUpdate
导航守卫,它还允许你取消导航:
2、捕获所有路由或404 Not Found路由
常规参数只匹配 url 片段之间的字符,用 /
分隔。如果我们想匹配任意路径,我们可以使用自定义的 路径参数 正则表达式,在 路径参数 后面的括号中加入 正则表达式 :
const routes = [// 将匹配所有内容并将其放在 `route.params.pathMatch` 下{ path: '/:pathMatch(.*)*', name: 'NotFound', component: NotFound },// 将匹配以 `/user-` 开头的所有内容,并将其放在 `route.params.afterUser` 下{ path: '/user-:afterUser(.*)', component: UserGeneric },]
解释:path: '/:pathMatch(.*)*'
● :pathMatch:动态路由参数名,可通过 $route.params.pathMatch 获取未匹配的路径片段
● (.*)*:正则表达式,匹配任意字符(包括 /),支持多级路径(如 /a/b/c)。
● * 结尾:允许将未匹配的路径分割为数组形式(如 /not/found 转换为 ['not', 'found']) 。
七、路由的匹配语法
1、在参数中自定义正则—(正则)
当定义像 :userId
这样的参数时,我们内部使用以下的正则 ([^/]+)
来从 URL 中提取参数。想象一下,两个路由 /:orderId
和 /:productName
,两者会匹配完全相同的 URL,所以我们需要一种方法来区分它们。
①最简单的方法就是在路径中添加一个静态部分来区分它们:
// 匹配 /o/3549 { path: '/o/:orderId' },
// 匹配 /p/books { path: '/p/:productName' },
②在括号中为参数指定一个自定义的正则:
// /:orderId -> 仅匹配数字 { path: '/:orderId(\\d+)' },
// /:productName -> 匹配其他任何内容 { path: '/:productName' },
注:确保转义反斜杠( \
),就像我们对 \d
(变成\\d
)所做的那样,在 JavaScript 中实际传递字符串中的反斜杠字符。
2、可重复的参数—*、+
如果你需要匹配具有多个部分的路由,如 /first/second/third
,你应该用 *
(0 个或多个)和 +
(1 个或多个)将参数标记为可重复:
const routes = [// /:chapters -> 匹配 /one, /one/two, /one/two/three, 等{ path: '/:chapters+' },// /:chapters -> 匹配 /, /one, /one/two, /one/two/three, 等{ path: '/:chapters*' },]
3、Sensitive 与 strict 路由配置
默认情况下,所有路由是不区分大小写的,并且能匹配带有或不带有尾部斜线的路由。例如,路由 /users
将匹配 /users
、/users/
、甚至 /Users/
。
这种行为可以通过
strict
和sensitive
选项来修改,它们既可以应用在整个全局路由上,又可以应用于当前路由上:默认:不区分大小写,能匹配带有或不带有尾部斜线的路由。
strict:不匹配带有尾部斜线的路由
sensitive:区分大小写
注意:不能写成Sensitive和Strict,大写开头是不起作用的!!!
const router = createRouter({history: createWebHistory(),routes: [// 将匹配 /users/posva 而非:// - /users/posva/ 当 strict: true// - /Users/posva 当 sensitive: true{ path: '/users/:id', sensitive: true },// 将匹配 /users, /Users, 以及 /users/42 而非 /users/ 或 /users/42/{ path: '/users/:id?' },],strict: true, // applies to all routes
})
4、可选参数—?
你也可以通过使用 ?
修饰符(0 个或 1 个)将一个参数标记为可选:
const routes = [// 匹配 /users 和 /users/posva{ path: '/users/:userId?' },// 匹配 /users 和 /users/42{ path: '/users/:userId(\\d+)?' },
]
请注意,*
在技术上也标志着一个参数是可选的,但 ?
参数不能重复。
八、嵌套路由
一个被渲染的组件也可以包含自己嵌套的 <router-view>
。
例如,如果我们在 User
组件的模板内添加一个 <router-view>
:
<!-- User.vue -->
<template><div class="user"><h2>User {{ $route.params.id }}</h2><router-view /></div>
</template>
要将组件渲染到这个嵌套的 router-view
中,我们需要在路由中配置 children
:
const routes = [{path: '/user/:id',component: User,children: [{// 当 /user/:id/profile 匹配成功// UserProfile 将被渲染到 User 的 <router-view> 内部path: 'profile',component: UserProfile,},{// 当 /user/:id/posts 匹配成功// UserPosts 将被渲染到 User 的 <router-view> 内部path: 'posts',component: UserPosts,},],},
]
注意,以 /
开头的嵌套路径将被视为根路径。这允许你利用组件嵌套,而不必使用嵌套的 URL。
children
配置只是另一个路由数组,就像 routes
本身一样。因此,你可以根据自己的需要,不断地嵌套视图。
忽略父组件
我们还可以仅利用路由的父子关系,但不嵌套路由组件。
为了实现这一点, 我们在父路由中省略了 component
和 components
选项
const routes = [{path: '/admin',children: [{ path: '', component: AdminOverview },{ path: 'users', component: AdminUserList },{ path: 'users/:id', component: AdminUserDetails },], },
]
由于父级没有指定路由组件,顶级 <router-view>
将跳过父级并仅使用子路由组件。
九、命名路由
当创建一个路由时,我们可以选择给路由一个 name
:
const routes = [{path: '/user/:username',name: 'profile', component: User}
]
然后我们可以使用
name
而不是path
来传递to
属性给<router-link>
:<router-link :to="{ name: 'profile', params: { username: 'erina' } }"> 注意不要漏掉:
<router-link to="/user/erina">
使用
name
有很多优点:
- 没有硬编码的 URL。
params
的自动编码/解码。- 防止你在 URL 中出现打字错误。
- 绕过路径排序,例如展示一个匹配相同路径但排序较低的路由。
所有路由的命名都必须是唯一的。如果为多条路由添加相同的命名,路由器只会保留最后那一条。
十、重定向
重定向也是通过 routes
配置来完成,下面例子是从 /home
重定向到 /
:
const routes = [{ path: '/home', redirect: '/' }]
重定向的目标也可以是一个命名的路由:
const routes = [{ path: '/home', redirect: { name: 'homepage' } }]
甚至是一个方法,动态返回重定向目标:
const routes = [{// /search/screens -> /search?q=screenspath: '/search/:searchText',redirect: to => {// 方法接收目标路由作为参数// return 重定向的字符串路径/路径对象return { path: '/search', query: { q: to.params.searchText } }},},{path: '/search',// ...},
]
在写 redirect
的时候,可以省略 component
配置,因为它从来没有被直接访问过,所以没有组件要渲染。唯一的例外是嵌套路由:如果一个路由记录有 children
和 redirect
属性,它也应该有 component
属性。
十一、别名
将 /
别名为 /home
,意味着当用户访问 /home
时,URL 仍然是 /home
,但会被匹配为用户正在访问 /
。
const routes = [{ path: '/', component: Homepage, alias: '/home' }]
通过别名,你可以自由地将 UI 结构映射到一个任意的 URL,而不受配置的嵌套结构的限制。使别名以 /
开头,以使嵌套路径中的路径成为绝对路径。你甚至可以将两者结合起来,用一个数组提供多个别名:
十二、路由组件传参
将 props 传递给路由组件
我们可以通过声明 prop 来在 User.vue
中删除对 $route
的直接依赖:
<template>
传props值:{{id}}
</template><script setup>
const props=defineProps({id:String
})</script><style scoped></style>
1、布尔模式
当 props
设置为 true
时,route.params
将被设置为组件的 props。
//props{path:'/props/:id',component: () => import('@/view/six.vue'),props: true},
2、对象模式
当 props
是一个对象时,它将原样设置为组件 props。当 props 是静态的时候很有用。
{path:'/props',component: () => import('@/view/six.vue'),props: { id: 1, name: 'lymm'}},
<template>传props值id:{{id}}传props值name:{{name}}
</template><script setup>const props=defineProps({id:String,name:String,
})//或
// const props=defineProps(
// ['id', 'name'] // 必须声明接收的 props
// )
console.log(props)
</script><style scoped></style>
3、函数模式
你可以创建一个返回 props 的函数。这允许你将参数转换为其他类型,将静态值与基于路由的值相结合等等。
const routes = [{///props?q=vuepath:'/props',component: () => import('@/view/six.vue'),props: route => ({ query: route.query.id })},
]
URL props?q=vue
将传递 {query: 'vue'}
作为 props 传给 six
组件。
请尽可能保持 props
函数为无状态的,因为它只会在路由发生变化时起作用。如果你需要状态来定义 props,请使用包装组件,这样 vue 才可以对状态变化做出反应。
<template>传props值id:{{query}}
</template><script setup>const props=defineProps({query:String,})console.log(props)
</script>
<style scoped></style>
十三、编程式导航
1、导航到不同位置
编程式导航:借助 router 的实例方法,通过编写代码来实现。
注:
你可以使用
$router
属性访问路由,例如this.$router.push(...)
。如果使用组合式 API,你可以通过调用 useRouter() 来访问路由器。
import {useRouter} from 'vue-router';
const router = useRouter();
①router.push
-- 这个方法会向 history 栈添加一个新的记录,所以,当用户点击浏览器后退按钮时,会回到之前的 URL。
②<router-link>
--内部会调用push这个方法,所以点击 <router-link :to="...">
相当于调用 router.push(...)
push方法的参数可以是一个字符串路径,或者一个描述地址的对象。例如:
// 字符串路径
router.push('/users/eduardo')// 带有路径的对象
router.push({ path: '/users/eduardo' })// 命名的路由,并加上参数,让路由建立 url
router.push({ name: 'user', params: { username: 'eduardo' } })// 带查询参数,结果是 /register?plan=private
router.push({ path: '/register', query: { plan: 'private' } })// 带 hash,结果是 /about#team
router.push({ path: '/about', hash: '#team' })
注意:如果提供了 path
,params
会被忽略(params只能和name组合)
const username = 'eduardo'
// 我们可以手动建立 url,但我们必须自己处理编码
router.push(`/user/${username}`) // -> /user/eduardo
// 同样
router.push({ path: `/user/${username}` }) // -> /user/eduardo
// 如果可能的话,使用 `name` 和 `params` 从自动 URL 编码中获益
router.push({ name: 'user', params: { username } }) // -> /user/eduardo
// `params` 不能与 `path` 一起使用
router.push({ path: '/user', params: { username } }) // -> /user
当指定 params
时,可提供 string
或 number
参数。任何其他类型(如对象、布尔等)都将被自动字符串化。
2、替换当前位置—replace
它的作用类似于 router.push
,唯一不同的是,它在导航时不会向 history 添加新记录,正如它的名字所暗示的那样——它取代了当前的条目。
声明式 | 编程式 |
|
|
也可以直接在传递给 router.push
的 to
参数中增加一个属性 replace: true
router.push({ path: '/home', replace: true })
// 相当于
router.replace({ path: '/home' })
router.replace('/home' )
3、横跨历史—go
该方法采用一个整数作为参数,表示在历史堆栈中前进或后退多少步,
例子
// 向前移动一条记录,与 router.forward() 相同
router.go(1)// 返回一条记录,与 router.back() 相同
router.go(-1)// 前进 3 条记录
router.go(3)// 如果没有那么多记录,静默失败
router.go(-100)
router.go(100)
十四、不同的历史模式
1、Hash 模式——createWebHashHistory()
hash 模式是用 createWebHashHistory()
创建的:
import { createRouter, createWebHashHistory } from 'vue-router'const router = createRouter({history: createWebHashHistory(),routes: [//...],
})
它在内部传递的实际 URL 之前使用了一个井号(#
)。由于这部分 URL 从未被发送到服务器,所以它不需要在服务器层面上进行任何特殊处理。不过,它在 SEO 中确实有不好的影响。如果你担心这个问题,可以使用 HTML5 模式。
使用HTML5:http://localhost:5174/one
使用Hash:http://localhost:5174/#/one
2、Memory 模式——createMemoryHistory(一般不用)
Memory 模式不会假定自己处于浏览器环境,因此不会与 URL 交互也不会自动触发初始导航。它是用 createMemoryHistory()
创建的,并且需要你在调用 app.use(router)
之后手动 push 到初始导航。
import { createRouter, createMemoryHistory } from 'vue-router'const router = createRouter({history: createMemoryHistory(),routes: [//...],})
虽然不推荐,你仍可以在浏览器应用程序中使用此模式,但请注意它不会有历史记录,这意味着你无法后退或前进。
注:
- Memory 模式不会与浏览器 URL 交互,也不会自动触发初始导航。
- 它的路由状态完全由 JavaScript 内存管理,因此浏览器地址栏的路径变化不会影响路由跳转 。
- Memory 模式通常用于 非浏览器环境(如 Node.js、SSR 或移动应用)。
- 若必须使用 Memory 模式,需通过 <RouterLink> 或 router.push() 主动触发导航
3、HTML5 模式——createWebHistory()
用 createWebHistory()
创建 HTML5 模式,推荐使用这个模式:
import { createRouter, createWebHistory } from 'vue-router'const router = createRouter({history: createWebHistory(),routes: [//...],
})
当使用这种历史模式时,URL 会看起来很 "正常",例如 https://example.com/user/id
。漂亮!
不过,问题来了。由于我们的应用是一个单页的客户端应用,如果没有适当的服务器配置,用户在浏览器中直接访问
https://example.com/user/id
,就会得到一个 404 错误。nginx需要配置:location / { try_files $uri $uri/ /index.html;}