Vue-Router 学习笔记

资源

项目地址

引入

根据 “路” 这个词的头脑风暴

路由

  • 路由是根据不同的 url 地址展示不同的内容或页面

    后端路由

  • 浏览器在地址栏中切换不同的url时,每次都向后台服务器发出请求,服务器响应请求,在后台拼接html文件传给前端显示, 返回不同的页面, 意味着浏览器会刷新页面,网速慢的话说不定屏幕全白再有新内容。后端路由的另外一个极大的问题就是前后端不分离。
  • 优点:分担了前端的压力,html和数据的拼接都是由服务器完成。
  • 缺点:当项目十分庞大时,加大了服务器端的压力,同时在浏览器端不能输入制定的url路径进行指定模块的访问。另外一个就是如果当前网速过慢,那将会延迟页面的加载,对用户体验不是很友好。
  • 举例:分配一个站点,服务器地址是:http://192.168.1.200:8899,在这个网站中提供了三个界面。
    1
    2
    3
    http://192.168.1.200:8899/index.html          主页
    http://192.168.1.200:8899/about/aboutus.html 关于我们页面
    http://192.168.1.200:8899/feedback.html 反馈界面
  • 当我们在浏览器输入 http://192.168.1.200:8899/index.html 访问界面的时候,web 服务器就会接收到这个请求,然后把 index.html 解析出来,并找到相应的 index.html 并展示出来,这就是路由的分发,路由的分发是通过路由功能来完成的。

    前端路由

  • 很重要的一点是页面不刷新,前端路由就是把不同路由对应不同的内容或页面的任务交给前端来做,每跳转到不同的URL都是使用 hash 路由. 随着(SPA)单页应用的不断普及,前后端开发分离,目前项目基本都使用前端路由,在项目使用期间页面不会重新加载。
  • 什么时候使用前端路由?在单页面应用,大部分页面结构不变,只改变部分内容的使用
  • 优点:
    • 用户体验好,前端页面的渲染和数据分离了,页面的渲染不会因为请求数据而阻塞。
    • 可以再浏览器中输入指定想要访问的url路径地址。
  • 虽然前端路由和后端路由的实现方式不一样,但是原理都有是相同的,在 H5 的 history Api 出来之前,前端路由的功能都是通过 hash 来实现的,hash 能兼容低版本的浏览器。

PS:后端路由每次访问一个页面都要向浏览器发送请求,然后服务端再响应解析,在这个过程中肯定会存在延迟,但是前端路由中访问一个新的界面的时候只是浏览器的路径改变了,没有和服务端交互「所以不存在延迟」,这个对用户体验来说是大大的提高。如下所示:

1
2
3
http://192.168.1.200:8080/#/index.html
http://192.168.1.200:8080/#/about/aboutus.html
http://192.168.1.200:8080/#/feedback.html

由于 web 服务器不会解析 # 后面的东西(所以通过 hash 能提高性能),但是客户端的 js 可以拿到 # 后面的东西,有一个方法是 window.location.hash 来读取,使用这个方法来匹配到不同的方法上,配合前端的一些逻辑操作就完成路由功能,剩下只是关心接口调用。
举例

1
http://www.xxx.com/path/a/b/c.html?key1=aaa && key2=bbb && key3=ccc#/path/d/e.html
  • 拆分这个地址

    1
    2
    3
    4
    5
    http:协议
    www.xxx.com:域名
    /path/a/b/c.html:路由,即服务器上的资源
    ?key1=aaa && key2=bbb && key3=ccc:Get 请求的参数,
    #/path/d/e.html:hash 也叫散列值,也叫锚点

    上面的 hash 是和浏览器交互的,其它的都是和服务器进行交互。
    通过上述我们知道,前端路由的实现方式有两种:

  • 改变 hash 值,监听 hashchange 事件,可以兼容低版本浏览器

  • 通过 H5 的 history API 来监听 popState 事件,使用 pushState 和 replaceState 实现

    hash 改变路由

    H5 的 history

    文档: https://developer.mozilla.org/zh-CN/docs/Web/API/History_API
    window 的 history 提供了对浏览器历史记录的访问功能,并且它暴露了一些方法和属性,让你在历史记录中自由的前进和后退,并且在 H5 中还可以操作历史记录中的数据。
    在 chrome 浏览器的调试窗口中在 console 中输入 window.history,会得到 history 的一些方法和属性,如下图所示
    图片

  • back():在历史记录中后退

    1
    history.back() ;
  • forward:在历史记录中前进

    1
    history.forward();
  • go():移动到指定的历史记录点

其中正数是前进「+1就是前进一个界面」,负责是后退的意思「-1就是后退一个界面」

1
history.go(-1)
  • length

hisgory 的属性,显示 history 的长度

  • pushState(data,title[,url]:给历史记录堆栈顶部添加一条记录
    1
    history.pushState(data,title[,url])
    使用 H5 的 history 的 pushState 可以代替 hash,并且更加优雅,点击各个导航没有刷新浏览器,并且点击浏览器的回退按钮,会显示上一次记录,这都是使用 h5 history 的 pushState 和监听 onpopstate 实现的,这就是一个简单的 SPA ,基本上实现了和上面 hash 一样的功能。

总结

  • 后端路由:每次访问都要向 server 发送一个请求,server 需要响应解析,会有延迟「网络不好更严重」

  • 前端路由:只是改变浏览器的地址,不刷新浏览器,不和服务端交互,所以性能有大大的提高「用户体验提高」,并且前端路由有两种实现方式。

    • 实现 hash 并监听 hashchange 事件来实现
    • 使用 H5 的 hisgory 的 pushState() 监听 popstate 方法来实现

      Vue Router

      概述

  • vue-router 和 vue.js 是深度集成的,适合用于单页面应用.传统的路由是用一些超链接来实现页面切换和跳转.而vue-router在单页面应用中,则是组件之间的切换.其本质就是:建立并管理 url 和对应组件之间的映射关系.

  • Vue Router 使构建单页面应用变得简单。使用 Vue.js ,我们已经可以通过组合组件来组成应用程序,当我们引入 Vue Router 后,我们需要做的是,将组件 (components) 映射到路由 (routes),然后告诉 Vue Router 在哪里渲染它们( & )。

    抓核心

    vue router 最核心的两个点

  • 路由配置

  • 路由使用

    环境搭建

  • 下载 vue, vue router

    1
    npm i vue vue-router -s

    总体思路

  • vue-router 是 vue.js 的一个插件,vue-router 要进行注册

  • 需要定义模板,即 template (很多个)

  • vue - router 的定义规则,完成模板和 hash 的匹配规则

  • 如果在页面中定义调用转发的路由 – a 标签

  • 模板的内容放在什么地方

    vue router 简易关系图

  • 关系图:如 ***vue router 简易关系图.xmind** 所示

  • 图片

    思考

  • router,routes, route 的区别*

  • router:一般指的就是路由实例.如$router.

  • *routes: *指router路由实例的routes API.用来配置多个route路由对象.

  • *route: *指的就是路由对象.例如;$route指的就是当前路由对象.

    vue router 的两种模式

  • 一般单页面应用是(SPA)不会请求页面而是只更新视图***.** vue-router提供了两种方式来实现前端路由:Hash模式和History模式,可以用mode参数来决定使用哪一种方式.

hash 模式

vue-router默认使用Hash模式.使用url的hash来模拟一个完整的url.此时url变化时,浏览器是不会重新加载的**.Hash(即#)是url的锚点,代表的是网页中的一个位置,仅仅改变#后面部分,浏览器只会滚动对应的位置,而不会重新加载页面.#仅仅只是对浏览器进行指导,而对服务端是完全没有作用的!它不会被包括在http请求中,故也不会重新加载页面**.同时hash发生变化时,url都会被浏览器记录下来,这样你就可以使用浏览器的后退了.

总结:Hash模式的本质就是通过改变#后面的值,实现浏览器渲染指定的组件**.**

history模式

history模式利用了HTML5 History新增的pushState()和replaceState()方法**.** 除了之前的back,forward,go方法,这两个新方法可以应用在浏览器历史记录的增加替换功能上.使用History模式,通过历史记录修改url,但它不会立即向后端发送请求. 注意点: 虽然History模式可以丢掉不美观的#,也可以正常的前进后退,但是刷新之后,此时浏览器就会访问服务器,在没有后台支持的情况下,此时就会得到一个404!官方文档给出的描述是:”不过这种模式要玩好,还需要后台配置支持.因为我们的应用是单个客户端应用,如果后台没有正确的配置,当用户直接访问时,就会返回404.所以呢,你要在服务端增加一个覆盖所有情况的的候选资源;如果url匹配不到任何静态资源,则应该返回同一个index.html页面.”

总结:History模式就是通过pushState()方法来对浏览器的浏览记录进行修改,来达到不用请求后端来渲染的效果.不过建议,实际项目还是使用history模式.

1
2
3
4
const router = new VueRouter({
  mode: 'history', //如果这里不写,路由默认为hash模式
  routes: [...]
})

history模式服务端配置(增添内容)

配置这个的原因是当你进入某个路由之后,再次刷新页面时(或者是浏览器直接输入某个路由路径时),当刷新页面,浏览器就会重新dns解析,tcp协议,这个时候会根据浏览器的url去服务器找对应资源,当然我们vue-router是为单页面服务的,对应的url在服务端是肯定没有静态资源的,就会出现404,当配置了以下url重写语句,注意是重写,不是重定向,不改变url的情况重写浏览器内容,重写到index.html,因为这个index.html使我们项目的入口,index.html里面会读取当时打包好的app.js,就可以读取到路由配置,以实现我们浏览器的url对应的路由页面。

hash模式不需要配置,因为浏览器会忽略#和?后面的参数

1
2
3
4
5
6
7
8
打包文件在根目录时,
location / {
try_files $uri $uri/ /index.html;
}
打包文件在非根目录时,
location /admin {
try_files $uri $uri/ /admin/index.html;
}

动态路由匹配

目的:如何将多路径映射至同一组件?如何在该组件下监听路径的变化?

本质:**动态路由匹配本质上就是通过url进行传参**

** **路由对象属性:

  • $route.path 类型: string 字符串,对应当前路由的路径,总是解析为绝对路径,如 “/foo/bar”。
  • $route.params 类型: Object 一个 key/value 对象,包含了动态片段和全匹配片段,如果没有路由参数,就是一个空对象。
  • $route.query 类型: Object 一个 key/value 对象,表示 URL 查询参数。例如,对于路径 /foo?user=1,则有 $route.query.user == 1,如果没有查询参数,则是个空对象。
  • $route .name 当前路由的名称。这里建议最好给每个路由对象命名,方便以后编程式导航.同时name必须唯一!
  • $route.hash 类型: string 当前路由的 hash 值 (带 #) ,如果没有 hash 值,则为空字符串。
  • $route.fullPath 类型: string 完成解析后的 URL,包含查询参数和 hash 的完整路径。
  • $route.matched 类型: Array 一个数组,包含当前路由的所有嵌套路径片段的路由记录 。路由记录就是 routes 配置数组中的对象副本 (还有在 children 数组)。 $route.redirectedFrom 如果存在重定向,即为重定向来源的路由的名字。

params 配置路由

1
2
3
4
5
routes:[{
//动态路径参数,以冒号开头
path:'/user/:id',
component:User
}]

就是使用params进行配置.像/user/foo和/user/bar都将映射到相同的路由.

  • 一个路径参数使用’:’冒号进行标记.
  • 当匹配到一个路由时,参数就会被设置到this.$route.params,可以在每个组件内使用.例如/user/foo在this.$route.params.id就为foo

图片

举例

1
2
3
routes:[
{path:'/user/:shot/foo/:id', component:shotCat}
]
1
2
3
4
5
6
7
<p>
<router-link to="/user/shot/foo">/user/shot/foo</router-link>  <!--无法匹配到对应路由-->
<router-link to="/user/shot/cat/foo">/user/shot/cat/foo</router-link> <!--无法匹配到对应路由-->
<router-link to="/user/foo/foo/foo">/user/foo/foo/foo</router-link> <!--成功匹配,$route.params.shot为foo;$route.params.cat为foo;-->
<router-link to="/user/shot/foo/cat">/user/shot/foo/cat</router-link>
<!--成功匹配,$route.params.shot为shot;$route.params.cat为cat;-->
</p>

总结:

  • 同一个路径可以匹配多个路由,此时,匹配的优先级就按照路由的定义顺序.谁先定义的,谁的优先级就最高.
  • 由于路由参数对组件实例是复用的.例如:/user/foo 和 /user/bar在使用路由参数时,复用的都是User组件.此时组件的生命周期钩子不会再被调用。如果你想路径切换时,进行一些初始化操作时,可以用以下两种解决办法:
    • 在组件内 watch $route 对象:
      1
      2
      3
      4
      5
      6
      7
      8
      const User = {
       template: '...',
       watch: {
         '$route' (to, from) {
           // 对路由变化作出响应...
         }
       }
      }
    • 使用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()
        }
      }
  • query进行配置传参*

在项目里我们可以通过上面提到的params进行传参.同时也可以用query进行传参. 比如: foo vue-router会自动将?后的id=foo封装进this.$route.query里. 此时,在组件里this.$route.query.id值为’foo’. 除了通过router-link的to属性. query也可以导航进行传参。

命名路由

目的:给路由起一个名字

如何使用?

可以通过一个名称来标识一个路由,特别是在链接一个路由,或者是执行一些跳转的时候。可以在创建 Router 实例的时候,在 routes 配置中给某个路由设置名称。

图片

图片

注意:**path 和 params 一同出现的时候,params 会被忽略掉,你需要提供路由的 name 或者提供完整的带有参数的 path,**此处需要注意。在使用编程式导航时,使用 this.$router.push() 改变当前路由也需要注意,path 和 params 是不能共存的:

1
2
3
4
5
const id = 123;
router.push({ name: 'user', params: { id } }); // -> /user/123
router.push({ path: `/user/${ id }` }); // -> /user/123
// 这里的 params 不生效,path 和 params 不能共存
router.push({ path: '/user', params: { id } }); // -> /user

声明式和编程式

什么是编程式导航

编程式导航就是在vue组件内部通过this.$router访问路由实例,完全用 js 去操作路由,进行路由映射,所以 它的作用是和是一样的!

编程式导航的使用场景

如果想在路由跳转前做点其他事情,例如权限验证等.但是用的话,就直接跳转了.此时就可以使用编程式导航!

编程式导航的写法

router.push() 方法

编程式导航用到router.push方法.该方法的参数可以是一个字符串路径,或者一个描述地址的对象.例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
//字符串
this.$router.push('home')


//对象
this.$ruter.push({path:'home'})


//命名路由
this.$router.push({name:'user',params:{userId:2333}})


//带查询参数,变成/register?plan=private

this.$router.push({path:’register’,query:{plan:’private’}})

注意: path和params是不能同时生效的!,否则params会被忽略掉.所以使用对象写法进行params传参时,要么就是path加冒号:,要么就是像上例中的’命名路由’.通过name和params进行传参.然而query却并不受影响,有没有path都可以进行传参.
*router.replace()方法 *

router.replace和router.push很像,写法一样.但实际效果不一样.push是向history里添加新记录.而replace是直接将当前浏览器history记录替换掉!

对比router.push() 和 router.replace() 的区别:

  • 用push方法,页面1跳转到页面2,你使用浏览器的后退可以回到页面1
  • 用replace方法,页面1被替换成页面2,你使用浏览器的后退,此时你回不到页面1,只能回到页面1的前一页,页面0.

router.replace() 使用场景

当你不想让用户回退到之前的页面时,常见于权限验证,验证后就不让用户回退到登录页重复验证.

router.go(n)方法

方法的参数就是一个整数,意思是在history记录中前进或后退多少步.类似window.history.go(n).这样就能控制页面前进或者后退多少步.

总结:

实际上不通过routes配置,也可以用下面这种方法直接在router-link上通过to进行传参. 关于to的更详细用法: 可以参考官方的api文档.

1
2
3
routes:[
{name:'shotCat',path:'/shotCat', component:shotCat}
]
1
2
3
4
5
6
<router-link :to="{ name:'shotCat',params:{paramId:'hello'},
query:{queryId:'world'}}">helloWorld</router-link>
<!--此时通过name匹配到路由对象shotCat.-->  
<router-link :to="{ path:'/shotCat',params:{paramId:'hello'},
query:{queryId:'world'}}">helloWorld</router-link> 
<!--此时通过path匹配到路由对象shotCat.但是!!!!!此时`paramId`并不能添加到`$route.params`里,只有`queryId`成功添加到`$route.query`-->

通过两个router-link.可以发现这种写法和编程式导航的规则一样, path和params是不能同时生效的! 所以最好给每个路由对象进行命名!
小结:

  • 等同于this.$router.push(). path和params是不能同时存在的!,想通过params,就得加上name属性.query不受影响.
  • 和this.$router.push()的实际效果也是一样的.
    • params参数都不会显示在url地址栏中.除了在路由中通过routes进行配置的.所以用户刷新页面后,params参数就会丢失!
    • query参数可以正常显示在url地址栏中.刷新页面后也不会丢失

通过to虽然可以进行params,query传参.但是注意此时页面url并不会改变!.所以你刷新页面后,params 参数就没有了,但是query的参数还在.

核心代码:如编程式导航.html 所示。

嵌套路由与命名视图

  • 嵌套路由:就是父路由嵌套子路由.url上就是/user嵌套两个子路由后就是/user/foo和/uer/bar.用一张图表示就是:
  • 图片

一个对应展示的就是一个组件 因此实现嵌套路由有两个要点:

  • 路由对象中定义子路由(嵌套子路由)
  • 组件内的使用.

路由对象定义子路由

图片

组件内 的使用

图片

图片

命名视图

  • 命名视图:就是一个组件里有多个视图进行展示.即包含有多个

如果一个组件有多个视图,来展示多个子组件.这个时候就需要用到命名视图

图片

重定向和别名

重定向其实就是通过路由拦截path,然后替换url跳转到redirect所指定的路由上. 重定向是通过 routes 配置来完成。

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
//从 /a 重定向到 /b
const router = new VueRouter({
routes:[
{path:'/a',rediret:'/b'}
]
})

///从 /a 重定向到 命名为'foo'的路由
const router = new VueRouter({
  routes: [
    { path: '/a', redirect: { name: 'foo' }}
  ]
})

//甚至是一个方法,动态返回重定向目标:
const router = new VueRouter({
  routes: [
    { path: '/a', redirect: to => {
      // 方法接收 目标路由 作为参数
      // return 重定向的 字符串路径/路径对象
  const { hash, params, query } = to
  //这里使用了ES6的解构写法,分别对应了to的hash模式,params,query参数.这里解构就不具体说明了.
        if (query.to === 'foo') {
          return { path: '/foo', query: null }
        }
        if (hash === '#baz') {
          return { name: 'baz', hash: '' }
        }
        if (params.id) {
          return '/with-params/:id'
        } else {
          return '/bar'
        }
    }}
  ]
})

别名

重定向是替url换路径,达到路由跳转.那别名就是一个路由有两个路径.两个路径都能跳转到该路由. 别名是在routes里的alias进行配置:

1
2
3
4
5
6
const router = new VueRouter({
//这时,路径'/fxxksky'和'/two-dogs' 都会跳转到A
  routes: [
    { path: '/fxxksky', component: A, alias: '/two-dogs' }
//当有多个别名时,alias也可以写成数组形式.  alias: ['/two-dogs', 'three-dogs','four-dogs','five-dogs'] 
  ]

})

路由组件传参
目的:通过传参将组件与路由解耦,使得组件的使用更加灵活。

路由传参,可以通过params和query进行传参.但这两种传参方式,本质上都是把参数放在url上,通过改变url进行的.这样就会造成参数和组件的高度耦合. 如果想传参的时候,可以更自由,摆脱url的束缚.这时就可以使用 route的props进行解耦,提高组件的复用,同时不改变url.

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
//路由配置:
const Hello = {
  props: ['name'], //使用route的props传参的时候,对应的组件一定要添加props进行接收,否则根本拿不到传参
  template: '<div>Hello {{ $route.params}}和{{this.name}}</div>'
  //如果this.name 有值,那么name已经成功成为组件的属性,传参成功
}
const router = new VueRouter({
mode: 'history',
  routes: [
    { path: '/', component: Hello }, // 没有传参  所以组件什么都拿不到
    { path: '/hello/:name', component: Hello, props: true }, //布尔模式: props 被设置为 true,此时route.params (即此处的name)将会被设置为组件属性。
    { path: '/static', component: Hello, props: { name: 'world' }},
// 对象模式: 此时就和params没什么关系了.此时的name将直接传给Hello组件.注意:此时的props需为静态!
    { path: '/dynamic/:years', component: Hello, props: dynamicPropsFn },
// 函数模式: 1,这个函数可以默认接受一个参数即当前路由对象.
// 2,这个函数返回的是一个对象.
// 3,在这个函数里你可以将静态值与路由相关值进行处理.
    { path: '/attrs', component: Hello, props: { name: 'attrs' }}
  ]
})

function dynamicPropsFn (route) {
  return {
    name: (new Date().getFullYear() + parseInt(route.params.years)) + '!'
  }
}
new Vue({
  router,
  el: '#app'
})
1
2
3
4
5
6
7
8
9
10
11
12
<!--html部分-->
    <div id="app">
      <h1>Route props</h1>
      <ul>
        <li><router-link to="/">/</router-link></li>
        <li><router-link to="/hello/you">/hello/you</router-link></li>
        <li><router-link to="/static">/static</router-link></li>
        <li><router-link to="/dynamic/1">/dynamic/1</router-link></li>
        <li><router-link to="/attrs">/attrs</router-link></li>
      </ul>
      <router-view></router-view>
    </div>

路由懒加载

利用 Vue 提供的异步组件以及 Webpack 提供的代码分割功能提高组件加载效率

vue主要用于单页面应用,此时webpack会打包大量文件,这样就会造成首页需要加载资源过多,首屏时间过长,给用户一种不太友好的体验. 如果使用路由懒加载,仅在你路由跳转的时候才加载相关页面.这样首页加载的东西少了,首屏时间也减少了. vueRouter的懒加载主要是靠Vue 的异步组件Webpack 的代码分割功能,轻松实现路由组件的懒加载。 比较简单的写法只需要将组件以promise形式引入即可.

1
2
3
4
5
6
routes:[
      path:'/',
      name:'HelloWorld',
      component:resolve=>require(['@/component/HelloWorld'],resolve)
  ]
  //此时HelloWorld组件则不需要在第一步import进来

把组件分块
把组件按组分块可以把路由下的所有组件都打包在同个异步块 (chunk) 中,并且在devtools 的network里面看到动态加载的组件名字.

前提条件:

  • Webpack版本 > 2.4
  • 需要在webpack.base.conf.js里面的output里面的filename下面加上chunkFileName
    1
    2
    3
    4
    5
    6
    7
    8
    9
    output: {
     path: config.build.assetsRoot,
     filename: '[name].js',
     // 需要配置的地方
     chunkFilename: '[name].js',
     publicPath: process.env.NODE_ENV === 'production'
       ? config.build.assetsPublicPath
       : config.dev.assetsPublicPath
    }
    此时在引入组件时的写法需要使用 命名 chunk,一个特殊的注释语法来提供 chunk name
    1
    2
    3
    const Foo = () => import(/* webpackChunkName: "group-foo" */ './Foo.vue')
    const Bar = () => import(/* webpackChunkName: "group-foo" */ './Bar.vue')
    const Baz = () => import(/* webpackChunkName: "group-foo" */ './Baz.vue')

    导航守卫

    路由导航守卫,通俗点说就是路由钩子.作用也和生命周期钩子类似,在路由跳转过程进行操作控制. 导航守卫有很多钩子,

导航守卫分类

  • 全局守卫:异步执行,每个路由跳转都会按顺序执行.
    • router.beforeEach 全局前置守卫
    • router.beforeResolve 全局解析守卫(2.5.0+) 在beforeRouteEnter调用之后调用.
    • router.afterEach *全局后置钩子 进入路由之后 *注意:不支持next(),只能写成这种形式router.afterEach((to, from) => {});

每个守卫方法接收三个参数:

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 proprouter.push 中的选项。
    • next(error): (2.4.0+) 如果传入 next 的参数是一个 Error 实例,则导航会被终止且该错误会被传递给 router.onError() 注册过的回调。
      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
      //1,可以在main.js 或者在单独的路由配置文件router.js中进行设置
      router.beforeEach((to, from, next) => { 
      ...
            next();
          });

      //2,也可以在组件内部设置
      this.$router.beforeEach((to, from, next) => { 
      ...
            next();
          });

      //3,对函数及next()的详细使用说明
          router.beforeEach((to, from, next) => { 
      //首先to和from 其实是一个路由对象,所以路由对象的属性都是可以获取到
      //例如:我想获取获取to的完整路径就是to.path.
      //获取to的子路由to.matched[0].
            next();//使用时,千万不能漏写next!!!
      //next()  表示直接进入下一个钩子.
      //next(false)  中断当前导航
      //next('/path路径')或者对象形式next({path:'/path路径'})  跳转到path路由地址
      //next({path:'/shotcat',name:'shotCat',replace:true,query:{logoin:true}...})  这种对象的写法,可以往里面添加很多.router-link 的 to prop 和 router.push 中的选项(具体可以查看api的官方文档)全都是可以添加进去的,再说明下,replace:true表示替换当前路由地址,常用于权限判断后的路由修改.
      //next(error)的用法,(需2.4.0+) 
          }).catch(()=>{
        //跳转失败页面
        next({ path: '/error', replace: true, query: { back: false }})
      })
      //如果你想跳转报错后,再回调做点其他的可以使用 router.onError()
      router.onError(callback => { 
            console.log('出错了!', callback);
          });
  • 路由独享的守卫: 即路由对象独享的守卫
    • beforeEnter:路由只独享这一个钩子,在routes里配置
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      const router = new VueRouter({
        routes: [
          {
            path: '/foo',
            component: Foo,
            beforeEnter: (to, from, next) => {
              // 使用方法和上面的beforeEach一样
            }
          }
        ]
      })
  • 组件内的守卫: 注意:这类路由钩子是写在组件内部的,
    • beforeRouteEnter 进入路由前,此时实例还没创建,无法获取到this
    • beforeRouteUpdate (2.2) 路由复用同一个组件时
    • *beforeRouteLeave *离开当前路由,此时可以用来保存数据,或数据初始化,或关闭定时器等等
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      //在组件内部进行配置,这里的函数用法也是和beforeEach一样
      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`
        }
      }

      路由元信息

  • 什么是路由元信息*

一句话概括:路由配置的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对象,并在里面设置了一个requiresAuth状态为true.
它有什么用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
router.beforeEach((to, from, next) => {
  if (to.matched.some(record => record.meta.requireAuth)) {
//数组some方法,如果meta.requiresAuth为ture,则返回true.此时,说明进入该路由前需要判断用户是否已经登录 
    if (!auth.loggedIn()) {   //如果没登录,则跳转到登录页
      next({
        path: '/login',
        query: { redirect: to.fullPath } 
      })
    } else {
      next()
    }
  } else {
    next() // 确保一定要调用 next()
  }
})

我们可以通过在meta里设置的状态,来判断是否需要进行登录验证.如果meta里的requiresAuth为true,则需要判断是否已经登录,没登录就跳转到登录页.如果已登录则继续跳转.path,params,query都可以存储信息,作为登录验证的状态标记.的确,它们也可以达到同样的效果.如果是少量单个的验证,使用它们问题不大. 但如果是多个路由都需要进行登录验证呢?path,params,query是把信息显性地存储在url上的.并且多个路径都把一个相同的状态信息加在url上.这样就使url不再单纯,并且也很不优雅美观. 所以要优雅要隐性地传递信息,就使用meta对象吧!

滚动行为

当切换路由时,可以使页面滚动到你想要的某个地方,或者是保持之前滚动的位置,这时你就需要使用scrollBehavior这个方法.

注意点:

  • 这里控制和记住的滚动位置都是仅对整个组件页面而言的,并不包含你组件里面其他的滚动条.
  • 这里路由的模式只能是history.因为它使用了History新增的pushState()
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    const router = new VueRouter({
    mode:'history',//这个不能忘,默认是hash模式
      routes: [...],
      scrollBehavior (to, from, savedPosition) {
         // to:要进入的目标路由对象,到哪里去.和导航守卫的beforeEach一样
    //from:离开的路由对象,哪里来
    //savedPosition: 点击前进/后退的时候记录值{x:?,y:?}.并且只有通过浏览器的前进后退才会触发.
        // return 期望滚动到哪个的位置 { x: number, y: number }或者是{ selector: string, offset? : { x: number, y: number }},这里selector接收字符串形式的hash,如'#foo',同时你还可以通过offset设置偏移,版本需要大于2.6+
    //举个实例
    if(savePosition) { //如果是浏览器的前进后退就,返回之前保存的位置
          return savePosition;
        }else if(to.hash) {//如果存在hash,就滚动到hash所在位置
          return {selector: to.hash}
        }else{
      return {x:0,y:0}//否则就滚动到顶部
    }
      }
    })

    数据获取

    数据可以在导航完成前获取或者导航完成后获取,以给予用户不同的用户体验。

有时候,进入某个路由后,需要从服务器获取数据。例如,在渲染用户信息时,你需要从服务器获取用户的数据。我们可以通过两种方式来实现:

  • 导航完成之后获取:先完成导航,然后在接下来的组件生命周期钩子中获取数据。在数据获取期间显示“加载中”之类的指示。
  • 导航完成之前获取:导航完成前,在路由进入的守卫中获取数据,在数据获取成功后执行导航。

从技术角度讲,两种方式都不错,就看你想要的用户体验是哪种。

过渡动画

给路由切换时添加过渡效果。

是基本的动态组件,所以我们可以用 组件给它添加一些过渡效果,如果你想让每个路由组件有各自的过渡效果,可以在各路由组件内使用 并设置不同的 name

vue router 工具

图片

图片

模块化开发

  • 如果使用 vue 开发移动端app ,那么页面跳转就非常多,路由使用的非常非常多,这样就更能体现出路由的强大之处。

  • 分析

    Vue Router 采坑记

  • query方式传参和接收参数

    • 传参
      1
      2
      3
      4
      5
      this.$router.push({
      path:'/xxx',
      query:{
      id:id
      }
    • 接收参数
      1
      this.$route.query.id
  • 备注:传参是this.$router,接收参数是this.$route*

  • params方式传参和接收参数

    • 传参
      1
      2
      3
      4
      5
      6
      this.$router.push({
      name:'xxx',
      params:{
      id:id
      }
      })
    • 接收参数
      1
      this.$route.params.id
  • 备注: params传参,push里面只能是 name:’xxxx’,不能是path:’/xxx’,因为params只能用name来引入路由,如果这里写成了path,接收参数页面会是undefined。*
    query相当于get请求,页面跳转的时候,可以在地址栏看到请求参数,而params相当于post请求,参数不会再地址栏中显示。

  • this.$router 和this.$route的区别

    • $route为当前router跳转对象,里面可以获取name、path、query、params等。
    • $router为VueRouter实例,想要导航到不同URL,则使用$router.push方法。
  • name 是什么?有什么用?

    • 注册时为该条路由取的名字
    • 重定向
      • 在重定向的时候直接用name 代表 path 进行跳转,是一种快捷语法。
        1
        redirect:{name:'about'} //name为注册路由时对应路由所取的名字,为上面的快捷用法
    • linkTo
      • 在router-link上也可使用注册路由时路由所取的name对href进行设置,点击link时会自动跳转到该name路由的path
        1
        <router-link :to="{name:'about'}" tag="li">work</router-link>
    • 一路多图
      • 是一个路由对应多个视图时对除了路由默认视图的视图进行标注,以便引入对应的路由。在路由切换时,一个路由可以对应多个视图router-view,这时候我们就需要区分不同的 router-view ,所以就需要取个名字。
        1
        <router-view name="addV"></router-view> <router-view></router-view> //----------------------------------------------------- ,{ path: '/document', name: 'Document', // component: components:{ //一个路由对应多个视图 default:main, //default为关键字 ,没有取名的路由视图,必须的。main为引入的组件名 addV:other //addV为附加的router-view上取的名字,other为引入的组件所取的名字 } }
  • 如何更改 link 的标签类型

    • 直接在ink标签上利用tag属性进行修改
      1
      <router-link :to="index" exact tag="li" class="class1" event="mouseover">
  • 如何更改激活样式的类名

    1
    <router-link to="/about" active-class="diy">about</router-link>
  • 路由变化,页面数据不刷新

    • 原因
      • 出现这种情况是因为依赖路由的params参数获取写在created生命周期里面,因为相同路由二次甚至多次加载的关系 没有达到监听,退出页面再进入另一个文章页面并不会运行created组件生命周期,导致文章数据还是第一次进入的数据
    • 解决方案
      • watch监听路由是否变化
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
         watch: {
        // 方法1
        '$route' (to, from) { //监听路由是否变化
        if(this.$route.params.articleId){// 判断条件1 判断传递值的变化
        //获取文章数据
        }
        }
        //方法2
        '$route'(to, from) {
        if (to.path == "/page") { /// 判断条件2 监听路由名 监听你从什么路由跳转过来的
        this.message = this.$route.query.msg
        }
        }
        }
    • 使用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()
      }
      }
  • 按需加载

一般配合 Vue-Router 使用,适用于大型应用,将应用分割成小的代码块,只在需要的时候才从服务器加载。

  • 实现方式:
    • 异步组件实现
    • es6 import
  • 好处:
    • 按需加载,节省首次加载实践,提高速度,也算是一个性能优化;
    • 组件只会加载一次,加载完成后会缓存下来,使用一个组件多次使用的场景
      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
      // 异步组件实现
      export default new Router({
      routes: [
      {
      path: '/test',
      name: 'test',
      component: resolve => require(['../components/Test'], resolve)
      },
      ]
      })
      // ES6 import
      const Test1 = () => import('../components/Test1')
      const Test2 = () => import('../components/Test2')
      export default new Router({
      routes: [
      {
      path: '/test1',
      name: 'test1',
      component: Test1
      },
      {
      path: '/test2',
      name: 'test2',
      component: Test2
      }
      ]
      })
    • 路由的懒加载
      1
      2
      3
      4
      // 方式一
      component: () => import("@/views/Login.vue")
      // 方式二
      component: resolve => require(["@/views/Register.vue"], resolve)