Vue-Router
Vue的路由
安装:
1 | npm install vue-router |
使用:
1 | import Vue from "vue"; |
起步
- 路由出口:
<router-view></router-view>
- 路由跳转:
<router-link to="url"></router-link>
1 | import Vue from "vue"; |
注入路由this就挂载新挂载了两个属性:
this.$router
访问路由器,和router
使用起来完全一样this.$route
访问当前路由
动态路由匹配
我们通常会在url中传递参数,这时就需要动态路由去匹配
1 | const User = { |
注意:
:id
是个占位符,谁到这个占位符,谁就是这个参数现在如果url是
/user/foo
或/user/bar
都会映射到这个路由并且可以通过
this.$route.params.id
来拿到ID
params
和query
的区别:
params
拿到的是占位符的东西query
拿到的是查询参数的东西
响应路由参数的变化
路由的变化,渲染同个组件,也就意味着组件的生命周期不会再呗调用
可以通过watch属性来监听$route
对象
1 | const User = { |
或者使用2.2中引入的beforeRouteUpdate
导航守卫:
1 | const User = { |
捕获所有路由或404
常规参数只会被/
分隔的URL片段中的字符。如果相匹配任意路劲,我们可以使用通配符
1 | { |
当使用一个通配符时,$route.params
内会自动添加一个名为 pathMatch
参数。它包含了 URL 通过通配符被匹配的部分:
1 | // 给出一个路由 { path: '/user-*' } |
匹配优先级
有时候,同一个路径可以匹配多个路由,此时,匹配的优先级就按照路由的定义顺序:谁先定义的,谁的优先级就最高。
嵌套路由
这里的 <router-view>
是最顶层的出口,渲染最高级路由匹配到的组件。同样地,一个被渲染组件同样可以包含自己的嵌套。例如,在 ``User组件的模板添加一个
要在嵌套的出口中渲染组件,需要在 VueRouter
的参数中使用 children
配置:
1 | <div id="app"> |
编程式导航
除了使用 <router-link>
创建 a 标签来定义导航链接,我们还可以借助 router 的实例方法,通过编写代码来实现。
router.push(location,onComplete?,onAbort?)
再Vue实例中,可以通过this.$router.push
来调用
1 | // 字符串 |
注意:如果提供了 path
,params
会被忽略,上述例子中的 query
并不属于这种情况。取而代之的是下面例子的做法,你需要提供路由的 name
或手写完整的带有参数的 path
:
router.replace(location,onComplete?,onAbort?)
跟 router.push
很像,唯一的不同就是,它不会向 history 添加新记录,而是跟它的方法名一样 —— 替换掉当前的 history 记录。
router.go(n)
1 | // 在浏览器记录中前进一步,等同于 history.forward() |
命名路由
有时候,通过一个名称来标识一个路由显得更方便一些,特别是在链接一个路由,或者是执行一些跳转的时候。你可以在创建 Router 实例的时候,在 routes
配置中给某个路由设置名称。
1 | const router = new VueRouter({ |
要链接到一个命名路由,可以给 router-link
的 to
属性传一个对象:
1 | <router-link :to="{ name: 'user', params: { userId: 123 }}">User</router-link> |
这跟代码调用 router.push()
是一回事:
1 | router.push({ name: 'user', params: { userId: 123 }}) |
这两种方式都会把路由导航到 /user/123
路径。
命名试图
有时候想同时 (同级) 展示多个视图,而不是嵌套展示,例如同一个页面中匹配通出现两个模板
一个视图使用一个组件渲染,因此对于同个路由,多个视图就需要多个组件。确保正确使用 components
配置 (带上 s):
1 | <router-view class="view one"></router-view> // Foo |
嵌套命名视图
1 | <!-- UserSettings.vue --> |
重定向和别名
重定向也是通过 routes
配置来完成,下面例子是从 /a
重定向到 /b
:
1 | const router = new VueRouter({ |
重定向的目标也可以是一个命名的路由:
1 | const router = new VueRouter({ |
甚至是一个方法,动态返回重定向目标:
1 | const router = new VueRouter({ |
别名
“重定向”的意思是,当用户访问 /a
时,URL 将会被替换成 /b
,然后匹配路由为 /b
,那么“别名”又是什么呢?
/a
的别名是 /b
,意味着,当用户访问 /b
时,URL 会保持为 /b
,但是路由匹配则为 /a
,就像用户访问 /a
一样。
上面对应的路由配置为:
1 | const router = new VueRouter({ |
“别名”的功能让你可以自由地将 UI 结构映射到任意的 URL,而不是受限于配置的嵌套路由结构。
路由组件的传参
在组件中使用 $route
会使之与其对应路由形成高度耦合,从而使组件只能在某些特定的 URL 上使用,限制了其灵活性。
使用 props
将组件和路由解耦:
取代与 $route
的耦合,通过 props
解耦
1 | const User = { |
布尔模式
如果 props
被设置为 true
,route.params
将会被设置为组件属性。
对象模式
如果 props
是一个对象,它会被按原样设置为组件属性。当 props
是静态的时候有用。
1 | const router = new VueRouter({ |
函数模式
你可以创建一个函数返回 props
。这样你便可以将参数转换成另一种类型,将静态值与基于路由的值结合等等。
1 | const router = new VueRouter({ |
URL /search?q=vue
会将 {query: 'vue'}
作为属性传递给 SearchUser
组件。
请尽可能保持 props
函数为无状态的,因为它只会在路由发生变化时起作用。如果你需要状态来定义 props
,请使用包装组件,这样 Vue 才可以对状态变化做出反应。
History模式
Vue-router
默认hash
模式——使用 URL 的 hash 来模拟一个完整的 URL,于是当 URL 改变时,页面不会重新加载。
如果不想要很丑的 hash,我们可以用路由的 history 模式,这种模式充分利用 history.pushState
API 来完成 URL 跳转而无须重新加载页面。
不过这种模式要玩好,还需要后台配置支持。因为我们的应用是个单页客户端应用,如果后台没有正确的配置,当用户在浏览器直接访问 http://oursite.com/user/id
就会返回 404。
所以呢,你要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html
页面,这个页面就是你 app 依赖的页面
后端配置例子
node.js
1 | const http = require('http') |
其他的请看官网介绍
警告
给个警告,因为这么做以后,你的服务器就不再返回 404 错误页面,因为对于所有路径都会返回 index.html
文件。为了避免这种情况,你应该在 Vue 应用里面覆盖所有的路由情况,然后在给出一个 404 页面。
1 | const router = new VueRouter({ |
或者,如果你使用 Node.js 服务器,你可以用服务端路由匹配到来的 URL,并在没有匹配到路由的时候返回 404,以实现回退。
过渡动效
router-view
是基本的动态组件,所以我们可以用 transition
组件给它添加一些过渡效果:
1 | <transition> |
单个路由的过渡
1 | const Foo = { |
基于路由的动态过渡
1 | <!-- 使用动态的 transition name --> |
获取数据
有时候,进入某个路由后,需要从服务器获取数据。例如,在渲染用户信息时,你需要从服务器获取用户的数据。我们可以通过两种方式来实现:
- 导航完成之后获取:先完成导航,然后在接下来的组件生命周期钩子中获取数据。在数据获取期间显示“加载中”之类的指示。
- 导航完成之前获取:导航完成前,在路由进入的守卫中获取数据,在数据获取成功后执行导航。
导航完成后获取数据
当你使用这种方式时,我们会马上导航和渲染组件,然后在组件的 created
钩子中获取数据。这让我们有机会在数据获取期间展示一个 loading 状态,还可以在不同视图间展示不同的 loading 状态。
假设我们有一个 Post
组件,需要基于 $route.params.id
获取文章数据:
1 | <template> |
在导航完成前获取数据
1 | export default { |
在为后面的视图获取数据时,用户会停留在当前的界面,因此建议在数据获取期间,显示一些进度条或者别的指示。如果数据获取失败,同样有必要展示一些全局的错误提醒。
滚动行为
使用前端路由,当切换到新路由时,想要页面滚到顶部,或者是保持原先的滚动位置,就像重新加载页面那样。 vue-router
能做到,而且更好,它让你可以自定义路由切换时页面如何滚动。
注意: 这个功能只在支持 history.pushState
的浏览器中可用。
当创建一个 Router 实例,你可以提供一个 scrollBehavior
方法:
1 | const router = new VueRouter({ |
如果返回一个 falsy (译者注:falsy 不是 false
的值,或者是一个空对象,那么不会发生滚动。
异步滚动
你也可以返回一个 Promise 来得出预期的位置描述:
1 | scrollBehavior (to, from, savedPosition) { |
将其挂载到从页面级别的过渡组件的事件上,令其滚动行为和页面过渡一起良好运行是可能的。但是考虑到用例的多样性和复杂性,我们仅提供这个原始的接口,以支持不同用户场景的具体实现
路由元信息
路由守卫
导航表示路由正在发生变化时触发
vue-router
提供的导航守卫主要用来通过跳转或取消的方式守卫导航。有多种机会植入路由导航过程中:全局的, 单个路由独享的, 或者组件级的。
记住参数或查询的改变并不会触发进入/离开的导航守卫。你可以通过观察 $route
对象来应对这些变化,或使用 beforeRouteUpdate
的组件内守卫。
全局前置守卫
使用router.beforeEach
注册一个全局前置守卫:
1 | const router = new VueRouter({ ... }); |
当一个导航触发时,全局前置守卫按照创建顺序调用。守卫是异步解析执行,此时导航在所有守卫 resolve 完之前一直处于 等待中。
每个守卫方法接收三个参数:
to: Route
: 即将要进入的目标 路由对象from: Route
: 当前导航正要离开的路由next: Function
: 一定要调用该方法来 resolve 这个钩子。执行效果依赖next
方法的调用参数。next()
: 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed (确认的)。next(false)
: 中断当前的导航。如果浏览器的 URL 改变了 (可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到from
路由对应的地址。next('/')
或者next({ path: '/' })
: 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。你可以向next
传递任意位置对象,且允许设置诸如replace: true
、name: 'home'
之类的选项以及任何用在router-link
的to
prop 或router.push
中的选项。next(error)
: (2.4.0+) 如果传入next
的参数是一个Error
实例,则导航会被终止且该错误会被传递给router.onError()
注册过的回调。
确保 next
函数在任何给定的导航守卫中都被严格调用一次。它可以出现多于一次,但是只能在所有的逻辑路径都不重叠的情况下,否则钩子永远都不会被解析或报错。这里有一个在用户未能验证身份时重定向到 /login
的示例:
他的作用就是:当路由发生变化的时候,可以通过这钩子来拦截捕获,然后进行相对应的操作,必须调用next()
,或者也可以使用next(路劲)
来跳转到其他的页面
全局后置钩子
作用就是再路由跳转完后执行的回调,这钩子不会接受next函数
1 | router.afterEach((to,from) => { |
路由独享的守卫
你可以在路由配置上直接定义 beforeEnter
守卫:
1 | const router = new VueRouter({ |
组件内的守卫
1 | const Foo = { |
Vue的数据响应式,如果一开始没有在data里声明,那么修改了值,UI就不会更新
React的Hooks中使用useState之后,我们使用setState,是新建了一个state,并不是更新了原本的state,
完整的导航解析流程
- 导航被触发。
- 在失活的组件里调用
beforeRouteLeave
守卫。 - 调用全局的
beforeEach
守卫。 - 在重用的组件里调用
beforeRouteUpdate
守卫 (2.2+)。 - 在路由配置里调用
beforeEnter
。 - 解析异步路由组件。
- 在被激活的组件里调用
beforeRouteEnter
。 - 调用全局的
beforeResolve
守卫 (2.5+)。 - 导航被确认。
- 调用全局的
afterEach
钩子。 - 触发 DOM 更新。
- 用创建好的实例调用
beforeRouteEnter
守卫中传给next
的回调函数。