简介
如何在工程里引入路由
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| // route.js import Vue from 'vue' import VueRouter from 'vue-router' Vue.use(VueRouter) export default new VueRouter({ routes: [ { path: '/foo', component: () => import(/* webpackChunkName: "foo" */ 'foo/index.vue'), name: 'foo', props: true }, { path: '/bar', component: Bar, name: 'bar', props: true } ] })
|
1 2 3 4 5 6 7 8 9 10 11 12
| // page/index.js import Vue from 'vue' import Tpl from './index.vue' import './style.scss' import router from './router.js' const init = async () => { new Vue({ router, render: (h) => h(Tpl) }).$mount('#app') } init()
|
1 2 3 4 5 6
| <div id="app"> <h1>Hello App!</h1> <!-- 路由出口 --> <!-- 路由匹配到的组件将渲染在这里 --> <router-view></router-view> </div>
|
经过前面的三个步骤后,就完成了在工程里引入路由的工作,接下来就可以使用设置好的路由配置进行路由跳转了
路由跳转
路由跳转方式有两种:router-link 组件导航跳转和编程跳转。
用于界面中有跳转按钮的场景,用户点击按钮后就会按 router-link 指定的路由方式跳转。
1 2 3 4 5 6 7 8 9 10 11 12 13
| <div id="app"> <h1>Hello App!</h1> <p> <!-- 使用 router-link 组件来导航. --> <!-- 通过传入 `to` 属性指定链接. --> <!-- <router-link> 默认会被渲染成一个 `<a>` 标签 --> <router-link to="/foo">Go to Foo</router-link> <router-link to="/bar">Go to Bar</router-link> </p> <!-- 路由出口 --> <!-- 路由匹配到的组件将渲染在这里 --> <router-view></router-view> </div>
|
可以通过 to 属性指定目标地址,默认渲染成带有正确链接的 标签,可以通过配置 tag 属性生成别的标签。另外,当目标路由成功激活时,链接元素自动设置一个表示激活的 CSS 类名。
<router-link> 比起写死的 <a href="..."> 会好一些,理由如下:
无论是 HTML5 history 模式还是 hash 模式,它的表现行为一致,所以,当你要切换路由模式,或者在 IE9 降级使用 hash 模式,无须作任何变动。
在 HTML5 history 模式下,router-link 会守卫点击事件,让浏览器不再重新加载页面。
当你在 HTML5 history 模式下使用 base 选项之后,所有的 to 属性都不需要写 (基路径) 了。
也可以通过 JS 代码实现路由跳转,语法如下:
1
| router.push(location, onComplete?, onAbort?)
|
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| // 字符串 router.push('home')
// 对象 router.push({ path: 'home' })
// 命名的路由 router.push({ name: 'user', params: { userId: '123' }})
// 带查询参数,变成 /register?plan=private router.push({ path: 'register', query: { plan: 'private' }})
// 带参数查询 router.push({ path: `/user/${userId}` }) // -> /user/123
// 这里的 params 不生效 router.push({ path: '/user', params: { userId }}) // -> /user
|
注意:如果提供了 path,params 会被忽略,但可以指定 query,或者提供路由的 name 或手写完整的带有参数的 path。
同样的规则也适用于 router-link 组件的 to 属性。
动态路由匹配
我们经常需要把某种模式匹配到的所有路由,全都映射到同个组件。例如,我们有一个 User 组件,对于所有 ID 各不相同的用户,都要使用这个组件来渲染。那么,我们可以在 vue-router 的路由路径中使用“动态路径参数”(dynamic segment) 来达到这个效果:
1 2 3 4 5 6 7 8 9 10
| const User = { template: '<div>User</div>' }
const router = new VueRouter({ routes: [ // 动态路径参数 以冒号开头 { path: '/user/:id', component: User } ] })
|
一个“路径参数”使用冒号 : 标记。当匹配到一个路由时,参数值会被设置到 this.$route.params,可以在每个组件内使用。于是,我们可以更新 User 的模板,输出当前用户的 ID:
1 2 3
| const User = { template: '<div>User {{ $route.params.id }}</div>' }
|
也可以在一个路由中设置多段“路径参数”,对应的值都会设置到 $route.params 中。例如:
| 模式 |
匹配路径 |
$route.params |
| /user/:username |
/user/evan |
{ username: ‘evan’ } |
| /user/:username/post/:post_id |
/user/evan/post/123 |
{ username: ‘evan’, post_id: ‘123’ } |
当使用路由参数时,例如从 /user/foo 导航到 /user/bar,原来的组件实例会被复用。因为两个路由都渲染同个组件,比起销毁再创建,复用则显得更加高效。不过,这也意味着组件的生命周期钩子不会再被调用。
复用组件时,想对路由参数的变化作出响应的话,你可以简单地 watch (监测变化) $route 对象:
1 2 3 4 5 6 7 8
| const User = { template: '...', watch: { $route(to, from) { // 对路由变化作出响应... } } }
|
或者使用 2.2 中引入的 beforeRouteUpdate 导航守卫:
1 2 3 4 5 6 7
| const User = { template: '...', beforeRouteUpdate (to, from, next) { // react to route changes... // don't forget to call next() } }
|
路由 { path: ‘*’ } 通常用于客户端 404 错误。
1 2 3 4 5 6 7 8
| { // 会匹配所有路径 path: '*' } { // 会匹配以 `/user-` 开头的任意路径 path: '/user-*' }
|
当使用一个通配符时,$route.params 内会自动添加一个名为 pathMatch 参数。它包含了 URL 通过通配符被匹配的部分:
1 2 3 4 5 6
| // 给出一个路由 { path: '/user-*' } this.$router.push('/user-admin') this.$route.params.pathMatch // 'admin' // 给出一个路由 { path: '*' } this.$router.push('/non-existing') this.$route.params.pathMatch // '/non-existing'
|
vue-router 使用 path-to-regexp 作为路径匹配引擎,所以支持很多高级的匹配模式。
有时候,同一个路径可以匹配多个路由,此时,匹配的优先级就按照路由的定义顺序:
谁先定义的,谁的优先级就最高。
嵌套路由
实际生活中的应用界面,通常由多层嵌套的组件组合而成。同样地,URL 中各段动态路径也按某种结构对应嵌套的各层组件,例如:
1 2 3 4 5 6 7 8
| /user/foo/profile /user/foo/posts +------------------+ +-----------------+ | User | | User | | +--------------+ | | +-------------+ | | | Profile | | +------------> | | Posts | | | | | | | | | | | +--------------+ | | +-------------+ | +------------------+ +-----------------+
|
对应的路由配置为:
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
| // index.vue <div id="app"> <router-view></router-view> </div>
// User.js const User = { template: ` <div class="user"> <h2>User {{ $route.params.id }}</h2> <router-view></router-view> </div> ` }
// route.js const router = new VueRouter({ 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 } ] } ] })
|
嵌套路由用于父组件想包含一个或多个子组件,并且父组件和子组件想同时加载。
当你访问 /user/foo 时,User 的出口是不会渲染任何东西,这是因为没有匹配到合适的子路由。如果你想要渲染点什么,可以提供一个空的子路由:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| const router = new VueRouter({ routes: [ { path: '/user/:id', component: User, children: [ // 当 /user/:id 匹配成功, // UserHome 会被渲染在 User 的 <router-view> 中 { path: '', component: UserHome },
// ...其他子路由 ] } ] })
|
命名路由
用 name 来标识一个路由会在路由跳转的时候方便一些。语法如下:
1 2 3 4 5 6 7 8 9
| const router = new VueRouter({ routes: [ { path: '/user/:userId', name: 'user', component: User } ] })
|
然后路由跳转的时候就可以这样写了:
1 2
| <router-link :to="{ name: 'user', params: { userId: 123 }}">User</router-link> router.push({ name: 'user', params: { userId: 123 }})
|
命名视图
有时候想同时 (同级) 展示多个视图,而不是嵌套展示,这个时候命名视图就派上用场了。你可以在界面中拥有多个单独命名的视图,而不是只有一个单独的 router-view 出口(如果 router-view 没有设置名字,那么默认为 default)。比如:
1 2 3
| <router-view class="view one"></router-view> <router-view class="view two" name="a"></router-view> <router-view class="view three" name="b"></router-view>
|
界面中会同时显示 3 个路由对应的 view,对应的路由配置为:
1 2 3 4 5 6 7 8 9 10 11 12
| const router = new VueRouter({ routes: [ { path: '/', components: { default: Foo, a: Bar, b: Baz } } ] })
|
命名视图可以多层嵌套。
重定向和别名
重定向也是通过 routes 配置来完成,下面例子是从 /a 重定向到 /b:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| const router = new VueRouter({ routes: [ { path: '/a', redirect: '/b' } ] })
const router = new VueRouter({ routes: [ { path: '/a', redirect: { name: 'b' }} ] })
const router = new VueRouter({ routes: [ { path: '/a', redirect: to => { // 方法接收 目标路由 作为参数 // return 重定向的 字符串路径/路径对象 }} ] })
|
需要注意的是,导航守卫并并不会对重定向前的目标(/a)起作用,而仅仅应用在重定向目标(/b)上。因此为 /a 路由添加一个 beforeEach 守卫并不会有任何效果。
/a 的别名是 /b,意味着,当用户访问 /b 时,URL 会保持为 /b,但是路由匹配则为 /a,就像用户访问 /a 一样。
上面对应的路由配置为:
1 2 3 4 5
| const router = new VueRouter({ routes: [ { path: '/a', component: A, alias: '/b' } ] })
|
“别名”的功能让你可以自由地将 UI 结构映射到任意的 URL,而不是受限于配置的嵌套路由结构。
路由组件传参
在组件中使用 $route 会使之与其对应路由形成高度耦合,导致组件不容易在别的地方使用,我们可以使用 props 将组件和路由解耦:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const User = { props: ['id'], template: '<div>User {{ id }}</div>' } const router = new VueRouter({ routes: [ { path: '/user/:id', component: User, props: true },
// 对于包含命名视图的路由,你必须分别为每个命名视图添加 `props` 选项: { path: '/user/:id', components: { default: User, sidebar: Sidebar }, props: { default: true, sidebar: false } } ] })
|
如果 props 被设置为 true,route.params 将会被设置为组件属性。
*对象模式
如果 props 是一个对象,它会被按原样设置为组件属性。但只有当 props 是静态的时候有用,不能是变量。
const router = new VueRouter({
routes: [
{ path: ‘/promotion/from-newsletter’, component: Promotion, props: { newsletterPopup: false } }
]
})
你可以创建一个函数返回 props。这样你便可以将参数转换成另一种类型,将静态值与基于路由的值结合等等。
1 2 3 4 5
| const router = new VueRouter({ routes: [ { path: '/search', component: SearchUser, props: (route) => ({ query: route.query.q }) } ] })
|
URL /search?q=vue 会将 {query: ‘vue’} 作为属性传递给 SearchUser 组件。
请尽可能保持 props 函数为无状态的,因为它只会在路由发生变化时起作用。如果你需要状态来定义 props,请使用包装组件,这样 Vue 才可以对状态变化做出反应。
HTML5 History 模式
vue-router 默认 hash 模式 —— 使用 URL 的 hash 来模拟一个完整的 URL,于是当 URL 改变时,页面不会重新加载。
如果不想要很丑的 hash,我们可以用路由的 history 模式,这种模式充分利用 history.pushState API 来完成 URL 跳转而无须重新加载页面。
1 2 3 4
| const router = new VueRouter({ mode: 'history', routes: [...] })
|
当你使用 history 模式时,URL 就像正常的 url,例如 http://yoursite.com/user/id,也好看!
不过这种模式要玩好,还需要后台配置支持。因为我们的应用是个单页客户端应用,如果后台没有正确的配置,当用户在浏览器直接访问 http://oursite.com/user/id 就会返回 404,这就不好看了。
所以呢,你要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面。
导航守卫
vue-router 提供的导航守卫主要用来通过跳转或取消的方式守卫导航。有多种机会植入路由导航过程中:全局的, 单个路由独享的, 或者组件级的。
- 全局守卫
你可以使用 router.beforeEach、router.afterEach 注册全局前置和后置守卫:
1 2 3 4 5 6 7 8 9 10
| const router = new VueRouter({ ... })
router.beforeEach((to, from, next) => { if (to.name !== 'Login' && !isAuthenticated) next({ name: 'Login' }) else next() })
router.afterEach((to, from) => { // ... })
|
你可以在路由配置上直接定义 beforeEnter 守卫:
1 2 3 4 5 6 7 8 9 10 11
| const router = new VueRouter({ routes: [ { path: '/foo', component: Foo, beforeEnter: (to, from, next) => { // ... } } ] })
|
可以在路由组件内直接定义以下路由导航守卫:
beforeRouteEnter
beforeRouteUpdate (2.2 新增)
beforeRouteLeave
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| const Foo = { template: `...`, beforeRouteEnter (to, from, next) { // 在渲染该组件的对应路由被 confirm 前调用 // 不!能!获取组件实例 `this` // 因为当守卫执行前,组件实例还没被创建 }, beforeRouteUpdate (to, from, next) { // 在当前路由改变,但是该组件被复用时调用 // 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候, // 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。 // 可以访问组件实例 `this` }, beforeRouteLeave (to, from, next) { // 导航离开该组件的对应路由时调用 // 可以访问组件实例 `this` } }
|
导航被触发。
在失活的组件里调用 beforeRouteLeave 守卫。
调用全局的 beforeEach 守卫。
在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
在路由配置里调用 beforeEnter。
解析异步路由组件。
在被激活的组件里调用 beforeRouteEnter。
调用全局的 beforeResolve 守卫 (2.5+)。
导航被确认。
调用全局的 afterEach 钩子。
触发 DOM 更新。
用创建好的实例调用 beforeRouteEnter 守卫中传给 next 的回调函数。
路由元信息
定义路由的时候可以配置 meta 字段:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const router = new VueRouter({ routes: [ { path: '/foo', component: Foo, children: [ { path: 'bar', component: Bar, // a meta field meta: { requiresAuth: true } } ] } ] })
|
那么如何访问这个 meta 字段呢?
一个路由匹配到的所有路由记录会暴露为 $route 对象 (还有在导航守卫中的路由对象) 的 $route.matched 数组。因此,我们需要遍历 $route.matched 来检查路由记录中的 meta 字段。
过渡动效
是基本的动态组件,所以我们可以用 组件给它添加一些过渡效果:
想让每个路由组件有各自的过渡效果,可以在各路由组件内使用 并设置不同的 name。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| const Foo = { template: ` <transition name="slide"> <div class="foo">...</div> </transition> ` }
const Bar = { template: ` <transition name="fade"> <div class="bar">...</div> </transition> ` }
|
也可以基于当前路由与目标路由的变化关系,动态设置过渡效果:
1 2 3 4 5 6 7 8 9 10 11 12 13
| <!-- 使用动态的 transition name --> <transition :name="transitionName"> <router-view></router-view> </transition> // 接着在父组件内 // watch $route 决定使用哪种过渡 watch: { '$route' (to, from) { const toDepth = to.path.split('/').length const fromDepth = from.path.split('/').length this.transitionName = toDepth < fromDepth ? 'slide-right' : 'slide-left' } }
|
数据获取
有时候,进入某个路由后,需要从服务器获取数据。例如,在渲染用户信息时,你需要从服务器获取用户的数据。我们可以通过两种方式来实现:
从技术角度讲,两种方式都不错 —— 就看你想要的用户体验是哪种。
滚动行为
使用前端路由,当切换到新路由时,想要页面滚到顶部,或者是保持原先的滚动位置,就像重新加载页面那样。 vue-router 能做到,而且更好,它让你可以自定义路由切换时页面如何滚动。
注意: 这个功能只在支持 history.pushState 的浏览器中可用。
当创建一个 Router 实例,你可以提供一个 scrollBehavior 方法:
1 2 3 4 5 6
| const router = new VueRouter({ routes: [...], scrollBehavior (to, from, savedPosition) { // return 期望滚动到哪个的位置 } })
|
scrollBehavior 方法接收 to 和 from 路由对象。第三个参数 savedPosition 当且仅当 popstate 导航 (通过浏览器的 前进/后退 按钮触发) 时才可用。
路由懒加载
当打包构建应用时,JavaScript 包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了。
结合 Vue 的异步组件和 Webpack 的代码分割功能,轻松实现路由组件的懒加载。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import Vue from 'vue' import VueRouter from 'vue-router' Vue.use(VueRouter) export default new VueRouter({ routes: [ { path: '/foo', component: () => import(/* webpackChunkName: "foo" */ '/path/foo.vue'), name: 'foo', props: true }, { path: '/bar', component: () => import(/* webpackChunkName: "bar" */ '/path/bar.vue'), name: 'bar', props: true }, ] })
|
参考文档