实际项目开发时,经常需要把匹配某种模式的路由映射到同一个组件。例如,有一个 Book 组件,对于所有 ID 各不相同的图书,都可以使用这个组件来渲染,这可以使用路径中的动态段(dynamic segment) 来实现。
继续《十四、使用 Vue Router 开发单页应用(1)》的项目,修改 App.vue ,使用 router-link 组件添加两个导航链接。代码如下:
首页
新闻
图书
视频
图书1
图书2
在 components 目录下新建 Book.vue wen文件。如下:
图书ID: {{ $route.params.id }}
接下来编辑 router 目录下的 index.js 文件,导入 Book 组件,并添加动态路径 /book/:id 的路由配置,如下:
import { createRouter, createWebHashHistory } from 'vue-router'
import Home from '@/components/Home'
import News from '@/components/News'
import Books from '@/components/Books'
import Videos from '@/components/Videos'
import Book from '@/components/Book'
export default createRouter({
history: createWebHashHistory(),
routes: [
{
path: '/',
component: Home,
},
{
path: '/news',
component: News,
},
{
path: '/books',
component: Books,
},
{
path: '/videos',
component: Videos,
},
{
path:'/book/:id',
component:Book
}
]
})
在终端窗口执行 npm run serve 命令,运行项目,打开浏览器,出现 图书1和图书2链接,单击其中任意一个。结果如下:
在同一个路由中可以有多个参数,它们将映射到 route.params 中的相应字段,如下表所示:
模式 | 匹配路径 | route.params |
---|---|---|
/user/:username | /user/evan | {username:‘evan’} |
/user/:username/post/:post_id | /user/evan/post/123 | {username:‘evan’,post_id:‘123’} |
除了 route.params 外,route 对象还提供了其它有用信息,如 route.query (如果 URL 中有查询参数)、route.hash 等。
URL 中带有查询参数的形式为 /book?id=1,这在传统的 Web 应用程序中很常见,根据查询参数想服务端请求数据。在单页应用程序开发中,也支持路径中的查询参数。
修改 App.vue 文件
首页
新闻
图书
视频
图书1
图书2
修改 Book.vue
图书ID: {{ $route.query.id }}
修改 index.js
import { createRouter, createWebHashHistory } from 'vue-router'
import Home from '@/components/Home'
import News from '@/components/News'
import Books from '@/components/Books'
import Videos from '@/components/Videos'
import Book from '@/components/Book'
export default createRouter({
history: createWebHashHistory(),
routes: [
{
path: '/',
component: Home,
},
{
path: '/news',
component: News,
},
{
path: '/books',
component: Books,
},
{
path: '/videos',
component: Videos,
},
{
path:'/book',
component:Book
}
]
})
大多数应用程序使用静态路由(如/news)和动态路由(如/book/1)就可以满足应用的需求,不过 Vue Router 也提供了更加强大的参数匹配能力。要匹配任何内容,可以使用自定义参数正则表达式,方法是在参数后面的圆括号中使用正则表达式。
当定义一个如 “:id”的参数时,Vue Router 在内部使用正则表达式“([^/]+)”(至少有一个不是斜杠 / 的字符)从 URL 中提取参数。
假设有两个路由 /:orderId 和 /:productName ,它们将匹配完全相同的 URL ,要想区分它们,最简单的方法是在路径中添加一个静态部分来区分。代码如下:
const routes = [
// 匹配 /o/3549
{ path:'/o/:orderId' },
// 匹配 /p/books
{ path:'/p/:productName' }
]
假设要限定 orderId 只能是数字,而 productName 可以是任何值,那么在参数 orderId 后的圆括号中使用正则表达式来说明。代码如下:
const routes = [
// /:orderId 只能匹配数字
{ path:'/o/:orderId(\\d+)' },
// /:productName 匹配任何值
{ path:'/p/:productName' }
]
可以使用修饰符“*”(零个或多个)、“+”(一个或多个)将参数标记为可重复的。如下:
const routes = [
// /:chapters -> 匹配 /one,/one/two./one/tow/three,etc
{ path:'/:chapters+' }
// /:chapters -> 匹配 /,/one,/one/two./one/tow/three,etc
{ path:'/:chapters*' }
]
这将给出一个 params 数组而不是字符串,并且在使用命名路由时也需要传递一个数组。如下:
// given { path:'/:chapters*',name:'chapters' },
router.resolve({ name:'chapters',params:{chapters: [] } }).href
// 结果:/
router.resolve({ name: 'chapters',params:{ chapters:['a','b'] }}).href
// 结果:/a/b
// given { path:'/:chapters+',name:'chapters' }
router.resolve({ name:'chapters',params:{ chapters:[] } }).href
// 因为 chapters 是空,这将抛出一个错误
还可以通过将 “*”和“+”添加到右括号后,与自定义正则表达式结合使用。
const router = [
// 只匹配数字
{ path:'/:chapters(\\d+)+' },
{ path:'/:chapters(\\d+)*' }
]
还可以使用“?”将参数标记为可选的。如下:
const routers = [
// 匹配 /users 和 /users/posva
{ path:'/users/:userId?' }
// 匹配 /users 和 /users/42
{ path:'/users/:userId(\\d+)?' }
]
在实际应用场景中,一个界面 UI 通常由多层嵌套的组件组合而成,URL 中的各段也按某种结构对应嵌套的各层组件。
路径 user/:id 映射到 User 组件,根据 ID 的不同,显示不同的用户信息。ID 为 1 的用户单击链接 user/1/profile ,将在用户1的视图中渲染 Profile 组件;单击 user/1/posts ,将在用户1的视图中渲染 Posts组件。
继续 14.2 节中的例子(将例子恢复为动态段),当单击“图书”链接时,以列表形式显示所有图书的书名,进一步单击单个书名链接,在 Books 视图中显示图书的详细信息。这可以通过嵌套路由来实现。
在 assets 目录下新建一个 books.js 文件,里面是图书数据。如下:
books.js
export default [
{ id: 1, title: '标题1', desc: '描述1' },
{ id: 2, title: '标题2', desc: '描述2' },
{ id: 3, title: '标题3', desc: '描述3' },
]
这里硬编码了图书数据,只是为了演示需要,真实场景中,图书数据应该是通过 Ajax 请求从服务端加载得到。
修改 Books.vue,以列表方式显示图书数据,添加导航链接,并使用 router-view 指定 Book 组件渲染的位置。如下:
Books.vue
图书列表
{{ book.title }}
注意删除 App.vue 中的图书1 和图书2 的 router-link 的配置。
修改 router 目录下的 index.js 文件,增加嵌套路由的配置。如下:
import { createRouter, createWebHashHistory } from 'vue-router'
import Home from '@/components/Home'
import News from '@/components/News'
import Books from '@/components/Books'
import Videos from '@/components/Videos'
import Book from '@/components/Book'
export default createRouter({
history: createWebHashHistory(),
routes: [
{
path: '/',
component: Home,
},
{
path: '/news',
component: News,
},
{
path: '/books',
component: Books,
children: [
{ path: '/book/:id', component: Book }
]
},
{
path: '/videos',
component: Videos,
},
]
})
说明:
Book.vue
图书ID:{{ $route.params.id }}
在终端窗口中运行项目,打开浏览器,单击“图书”链接后,任选一本图书,结果如下:
在实际场景中,当单击某本图书链接时,应该向服务器发起 Ajax 请求来获取图书详细数据,于是想到在 Book 组件中通过生命周期钩子函数来实现,然而,这行不通。
因为当两个路由都渲染同一个组件时,如从 book/1 导航到 book/2 时,Vue 会复用先前的 Book 实例,比起销毁旧实例再创建新实例,复用会更加高效。但是这就意味着组件间的生命周期钩子不会再被调用,所以也就无法在生命周期构钩子中去根据路由参数的变化更新数据。
要对同一组件的路由参数更改做出响应,只需监听 route.params 即可。
修改 Book.vue ,当路由参数变化时,更新图书详细数据。如下:
Book.vue
图书ID:{{ book.id }}
标题:{{ book.title }}
描述:{{ book.desc }}
说明
(1)只有路由参数发生变化时,route.params 的监听才会被调用,这意味着第一次渲染 Book 组件时,通过 route.params 的监听器是得不到数据的,因此在 created 钩子中先获取第一次渲染时的数据。当然,也可以向 watch 方法传入一个选项对象作为第3个参数,设置 immediate 选项参数为 true ,使监听器回调函数在监听开始后立即执行,即不需要在 created 钩子中先获取一次数据。如下:
created(){
// this.book = Books.find((item) => item.id == this.$route.params.id);
this.$watch(
() => this.$route.params,
(toParams) => {
this.book = Books.find((item) => item.id == toParams.id);
}
)
}
(2)route.params 监听器回调函数的 toParams 参数表示即将进入的目标路由的参数,该函数还可以带一个 previousParams 参数,表示当前导航正要离开的路由的参数。
运行项目,单击不同的图书链接将显示对应图书的详细信息,如图:
除了监听 route 对象外,还可以利用 Vue Router 中的导航守卫(navigation guard):beforeRouteUpdate,可以把它理解为是针对路由的一个钩子函数。
修改 Book.vue 删除 route.params 监听器,改用 beforeRouteUpdate 来实现。如下:
图书ID:{{ book.id }}
标题:{{ book.title }}
描述:{{ book.desc }}
beforeRouteUpdate 在当前路由改变,但是该组价被复用时调用,它有两个常用的参数。to 表示即将进入的目标路由位置对象;from 表示当前导航正要离开的路由位置对象。此处只用到了参数 to。