【Vue基础】-路由Vue Router

368 阅读3分钟

花了几天终于把所有关于Vue Router3的知识点都过了一遍,中途参考了官网及对应讲师的录播课,开始表演!!!

起步

<router-link/>: 类似一个跳转页面的"按钮"

  • to属性的用法:
...
    <router-link to="/foo">Home</router-link>
    // 解析成:<a href="#/foo">Home</a>
    
    <router-link :to="{ path: '/foo' }">Home</router-link>
    // 解析成:<a href="#/foo">Home</a>
    
    <router-link to="foo">Go to Foo</router-link>
    // 不要这样写,这个地方还是path的意思,但没有/前缀,就不是根路径,不加/会发生紊乱,加上/路径层级跟清晰且不易出错
    
     **正确用法**
      <router-link :to="{ name: 'sys',params: { username: 'liuwenxiang' } }">系统默认路由</router-link>
      // 有params,地址不会拼接在url中
      <router-link :to="{ name: 'sys',query: { username: 'liuwenxiang' } }">系统默认路由</router-link>
      // 有query,地址就拼接成 .../sys?username=liuwenxiang
      <router-link :to="{ path: '/sys',query: { username: 'liuwenxiang' } }">系统默认路由</router-link> =
      // 有query,地址就拼接成 .../sys?username=liuwenxiang
     
     **错误用法**
     <router-link :to="{ path: '/sys',params: { username: 'liuwenxiang' } }">系统默认路由</router-link>
     // 不能通过$route.params.username取出值,因为path会覆盖params,所以path只能和query匹配!!!!
...

Tips:params类似于post请求地址栏不带参数,而query类似get请求于地址栏携带参数;namequery、params都可以搭配,path只能和query搭配使用。总之,params只能和name搭配,而params与path不能共存会覆盖。因为在路由中name不能重复,而path不具备唯一性,如果提供了path,则params会被忽略。

  • replace属性的用法:
    <router-link :to="{ path: '/sys'}" replace></router-link>
    // 调用router.replace(),而不是router.push(),即导航后不会留下此路由的 history 记录
  • append属性的用法:
    <router-link to="sys" append>append系统默认路由</router-link>
    // 前面提到过这个sys是路径,并且最好要加上/。如果没加/,可以搭配append使用,从其他路由地址栏跳转到这个路由后,路边地址变成:其他路由地址拼接这个路由也就是/sys,包括自己跳自己一样最终变成.../sys/sys,而如果使用的是to="/sys"则搭配append无效
  • tag属性的用法:
   <router-link to="/sys" tag="li">系统默认路由</router-link>
   // 不加tag属性编译成a标签,而tag="li"则编译成li标签,属性值也可以是其他标签名
  • exact属性的用法:
   <router-link to="/sys" exact>路由1</router-link>
   <router-link exact :to="{ path: '/sys',query: { username: 'liuwenxiang' } }">路由2</router-link>
   // 如果不加exact,点击路由2会让路由1也加上部分 class="router-link-active"。加了exact后,点击路由不会让路由加上任何class,即"精确的class"
  • active-class属性的用法:
    <router-link to="/sys">路由1</router-link>
    <router-link exact :to="{ path: '/sys',query: { username: 'liuwenxiang' } }">路由2</router-link>
    // 点击路由2,可以发现路由1,编译成<a href="#/sys" class="router-link-active">路由1</a>,加了个默认值class名: class="router-link-active"
  • event属性的用法:
   <router-link to="/sys" event="mousemove">系统默认路由</router-link>
   // 默认事件是鼠标点击,这里是mousemove鼠标划入事件,键盘事件也生效比如keyup
  • exact-active-class属性的用法:
   <router-link to="/sys">系统默认路由</router-link>
   // 点击这个路由,编译成<a href="#/sys" class="router-link-exact-active router-link-active" aria-current="page">系统默认路由</a> ,追加一个class的作用而已
  • aria-current-value属性的用法:
    <router-link to="/sys">系统默认路由</router-link>
    // 默认执行的是:<router-link to="/sys" aria-current-value="page">系统默认路由</router-link>,编译成:<a href="#/sys" class="router-link-exact-active router-link-active" aria-current="page">系统默认路由</a>,追加了aria-current="page"的属性和属性值,还有其他属性值如:step、date等    

<router-view/>: 路由出口,路由渲染的地方

  • name属性的用法:
...
    <router-view name="example"></router-view>
    // 如果 `<router-view>`设置了名称,则会渲染对应的路由配置中 `components` 下的相应组件
...
...
    {
        path: "/sys",
        components: { example: sysCpn }
    },
...
$route (路由信息对象,包含基本信息)$router (VueRouter的实例,包含跳转方法、钩子函数等)
path、pararms、query、hash、fullPath、matched、name、meta(matched:是一个数组,包含当前匹配的路径中所包含的所有片段所对应的配置参数对象),图解:image.pngpush、go、replace、back、resolve、currentRoute等,图解:image.png

1. 动态路由匹配

应用场景: 把/user/foo 和 /user/bar映射到相同的路由

...
    <router-link to="/dCpn/123" tag="button">路由1</router-link>
    <router-link to="/dCpn/456" tag="button">路由2</router-link>
    <router-link :to="{path:'/dCpn/789',query:{age:21}}"  tag="button">路由3</router-link>
    // 注意:这里params和query都存在值。$route.params:{ "name": "456" };$route.query:{ "age": "21" },这里其实也预示着path也承担着params的职责,path会覆盖params!
...
...
    { path: "/dCpn/:name", component: dCpn }
...
...
    ** 
    1.路由1组件内的值:$route.params:{ "name": "123" },可以通过$route.params.name取值。如果path: "/dCpn/:name/:age",$route.params:{ "name": xxx , "age": xxx}同存一个对象中。
    2.如果dCpn的路由被渲染过了,意味着组件的生命周期钩子不会再被调用,
    **    
...
    // 如何对路由参数的变化作出响应呢???
...
  watch: {
    $route(newVal, oldVal) {
      // 对路由变化作出响应...
    }
  }
...

2. 嵌套路由

    // 使用children
...
    const router = new VueRouter({
      routes: [
        {
          path: '/user/:id',
          component: User,
          children: [
            // 当 /user/:id 匹配成功,
            // UserHome 会被渲染在 User 的 <router-view> 中
            { path: '', component: UserHome }
            // ...其他子路由
          ]
        }
      ]
})
...

3. 编程式导航

语法:

router.push(location, onComplete?, onAbort?):JS原生用法,window.history.go(n)。在 v3.2.0 中,可以通过使用 router.push 的两个可选的回调函数:onComplete 和 onAbort 来暴露导航故障

router.replace(location, onComplete?, onAbort?):JS原生用法,window.history.go(n)。同上。

router.go(n):JS原生用法,window.history.go(n)

    ** 声明式导航:<router-link :to="..."> 编程式导航:router.push(...) **
    
    // 字符串
    router.push('home')
    // 对象
    router.push({ path: 'home' })
    // 命名的路由
    router.push({ name: 'user', params: { userId: '123' }})
    // 带查询参数,变成 /register?plan=private
    router.push({ path: 'register', query: { plan: 'private' }})
    
    ** push和replace用法一样,只是replace不会在浏览器中留下记录 **

4. 命名路由

    **这是通过name来标识一个路由,而一般是通过path来标识路由
    <router-link :to="{ name: 'user', params: { userId: 123 }}">路由1</router-link>
    // 同上:router.push({ name: 'user', params: { userId: 123 } })

5. 命名视图

...
        ** 同时 (同级) 展示多个视图,而不是嵌套展示,如sidebar和main两个视图,可以创造出不只有一个单独的出口**
    {
        path: "/sys",
        components: { example: sysCpn }, // 注意components加了复数s
        name: "sys"
    },  
...
...
    <router-view name="example"></router-view>
...

6. 重定向和别名

...
    **redirect 从 `/aCpn` 重定向到 `/bCpn` **
    routes: [
        { path: '/aCpn', redirect: '/bCpn' }
        // 其写法也可以:{ path: '/aCpn', redirect: { name: 'bCpn' }}
         
        //{ path: '/aCpn', redirect: to => {
            // // 方法接收 目标路由 作为参数  return 重定向的 字符串路径/路径对象
        //}}
    ]
...   
...
    **alias 指的是url的重定向,即/a可以到达/sys的页面,只是url不一样**
    {
        path: "/sys",
        components: { example: sysCpn },
        alias: ["/b"], // 这里最好不要写成"b",而是"/b",以免造成歧义
        name: "sys"
    },
...

7. 路由组件传参

    // $route传参params和query,让组件和路由高度耦合,于是提高了props进行参数解耦
    
用法1:
...
    { path: "/dCpn/:name", component: dCpn, props: true } 
    // 如果 `props` 被设置为 `true`,`route.params` 将会被设置为组件属性
...
...
    <router-link to="/dCpn/456" tag="button">路由1</router-link>
...
...
    props: {
        name: {
            type: String,
            default: "liu_xiao",
            required: true
        }
    } // 直接使用name,代替使用$route.params.name
...
用法2:
...
    {
        path: "/dCpn",
        component: dCpn,
        props: { name: 'world' }
    },
...
...
    <router-link to="/dCpn" tag="button">路由2</router-link>
...
...
    props: {
        name: {
            type: String,
            default: "liu_xiao",
            required: true
        }
    } // 直接使用name,代替使用$route.params.name
...

8. History和Hash模式

    ** History模式:History模式:**
    const router = new VueRouter({
      mode: 'history', // 'hash'
      routes: [...]
    })
    // 如:http://127.0.0.1:5500/src/js/2.html 没有#/。如:http://127.0.0.1:5500/src/js/2.html#/ 地址栏追加一个#/就是hash模式
    // 面试被问到过:history刷新会丢,需要服务端配置404的默认已有的匹配页面。hash前端本地刷新不会丢!!!

9. 导航守卫

导航守卫是Vue Router的重点和面试必问点,所以这个部分好好总结下(指的是Vue Router3的内容)!!!

可参考Vue Router3官网中共的导航守卫

  • 全局前置守卫: router.beforeEach((to, from, next) => { // ... }) // 路由跳转之前

  • 全局解析守卫: router.beforeResolve((to, from, next) => { // ... }) // 和beforeEach特别类似,只是导航被确认之前,同时组件内守卫和异步路由组件被解析之后就调用解析守卫

  • 全局后置钩子: router.afterEach((to, from) => { // ... }) // 路由跳转之前

  • 路由独享守卫: 路由对象VueRouter中调用

    const router = new VueRouter({
      routes: [
        {
          path: '/foo',
          component: Foo,
          beforeEnter: (to, from, next) => {
            // ...
          }
        }
      ]
    })
  • 组件内守卫 进入组件前: 组件内部调用
    const Foo = {
      template: `...`,
      beforeRouteEnter(to, from, next) {
        // 在渲染该组件的对应路由被 confirm 前调用
        // 不能获取组件实例 `this`, 因为当守卫执行前,组件实例还没被创建
      }
    }
  • 组件内守卫 组件重新加载时: 组件内部调用
    const Foo = {
      template: `...`,
      beforeRouteUpdate(to, from, next) {
        // 在当前路由改变,但是该组件被复用时调用:如从"/eCpn?id=1"到"/eCpn?id=2",从"/foo/:id"到"/foo/2"
        // 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
        // 可以访问组件实例 `this`
      },
    }
  • 组件内守卫 离开组件时: 组件内部调用
    const Foo = {
      template: `...`,
      beforeRouteLeave(to, from, next) {
        // 导航离开该组件的对应路由时调用
        // 可以访问组件实例 `this`
      }
    }

Tips;tofrom返回的都是$route信息;next: Function,执行效果依赖next方法的调用参数;next()next(false)next('/')((或者这种写法:next({ path: '/' }))、next(error)

image.png

10. 路由元信息

...
 routes: [
    {
      path: '/foo',
      component: Foo,
      children: [
        {
          path: 'bar',
          component: Bar,
          // a meta field
          meta: { requiresAuth: true }
        }
      ]
    }
  ]
...

...
 { path: "/eCpn", component: eCpn, meta: { required: true } },
...

...
 router.beforeEach((to, from, next) => {
  // 这个部分的代码实际就是说明了权限登录,是通过meta路由元信息来判断的,组件有无这个权限字段如:require,用户有无登录来判断是否跳入登录页面
  let isFlag = to.matched.some(record => record.meta.required);
  if (isFlag) {
    console.log("有权限");
    if (!auth.login()) {
      console.log("有权限且 未登录");
      // next({ path: '/loginCpn'})
      next({ name: "login" }); //这个next中放的时path信息而不是name信息
      // return{
      //   path: '/loginCpn'
      // }
    } else {
      console.log("有权限且 登录了");
      next();
    }
  } else {
    console.log("无权限");
    next();
  }
});
...

11. 过渡动效

...
  // 这个silde和css中的class名有关联关系
  <transition name="slide">
    <router-view></router-view>
  </transition>
...

...
<style>
    .slide-enter-active,
    .slide-leave-active {
      transition: all .5s;
    }

    .slide-enter {
      transform: translateX(0);
    }

    .slide-leave-to {
      transform: translateX(200px);
    }
</style>
...

12. 数据获取

应用场景: 数据获取期间展示一个 loading 状态,还可以在不同视图间展示不同的 loading 状态。 实现逻辑时:导航完成后获取数据。而官网也提供了另一种思路:导航完成前获取数据,这里不做推荐也不做赘述!

...
 // 比较常规的操作,在请求数据前和请求数据后给Boolean值取反即可
 methods: {
    fetchData () {
      this.error = this.post = null
      this.loading = true
      // replace getPost with your data fetching util / API wrapper
      getPost(this.$route.params.id, (err, post) => {
        this.loading = false
        if (err) {
          this.error = err.toString()
        } else {
          this.post = post
        }
      })
    }
  }
...

13. 滚动行为

应用场景: 当切换到新路由时,想要页面滚到顶部,或者是保持原先的滚动位置,就像重新加载页面那样,更多用法参考官网

...
    const router = new VueRouter({
      routes, // (缩写) 相当于 routes: routes
      mode: "hash",
      scrollBehavior (to, from, savedPosition) {//滚动行为
          if(to.path = ''){
              return {x:0,y:0}
          }
      }

     // 异步滚动
     scrollBehavior (to, from, savedPosition) {
       return new Promise((resolve, reject) => {
         setTimeout(() => {
           resolve({ x: 0, y: 0 })
         }, 500)
       })
     }
     
    });
...

14. 路由懒加载

应用场景:把不同路由对应的组件分割成不同的代码块(即代码分割),当路由被访问的时候才加载对应组件,优化页面加载速度

...
    const Foo = () => import('./Foo.vue')
...

...
    routes: [{ path: '/foo', component: Foo }]
...

15. 导航故障

个人觉得没啥用处,这里不做赘述,更多用法参考官网!!!

终于把Vue Router3的官网过了一遍,终于可以撒❀❀❀啦!!!