【笔记】vue-router

井旺
2023-12-01



一、引入

vue-router可以大幅度提升网站性能。只要首页加载完毕,在不同页面之间的导航就会变得飞快,因为所有的HTML、Javascript以及CSS都已经被下载下来了。

建议浏览一下核心概念再开始使用哦!

二、核心概念

1.Html5的History模式

vue-router默认使用URL hash来存储路径。自从有了HTML5 history API,就不用非得跳转一个新页面来访问。
在创建router对象时,添加设置mode: 'history',即可。

2.动态路由

若要在路由中添加动态参数,例如:/user/1234(userId),就要使用动态路由了:
vue-router支持动态路径匹配,也就是说可以通过使用一种专门的语法来指定路径规则,而所有匹配到该规则的路由下的组件都能被访问到。
在路由表中设置path:"/user/:userId"即可。

vue-router使用path-to-regexp库来实现这项功能,Express和Koa以及react都使用这种语法。

在组件实例中,想要获取动态参数,首先通过this.$route获取当前路由对象,它的params即动态参数列表:

{
	”userId“: ”1234“
}

这里拿到的是字符串,要想当做数值使用,则需使用parseFloat或Number自行转换

当然,多段动态内容也是可以的:

"/user/:userId/page/:pageId"

3.响应路由变化

在使用动态路由后,动态参数发生变化时,由于组件被复用,因此mounted并不会被调用,这时就需要用到beforeRouteUpdate导航守卫(guard):

<template>
  <div v-if="state === 'loading'">
    Loading user…
  </div>
  <div>
    <h1>User: {{ userInfo.name }}</h1>
    ... etc ...
  </div>
</template>
<script>
  export default {
    data: () => ({
      state: 'loading',
      userInfo: undefined
    }),
    mounted() {
      this.init();
    },
    beforeRouteUpdate(to, from, next) {
      this.state = 'loading';
      this.init();
      next();
    },
    methods: {
      init() {
        fetch(`/api/user/${this.$route.params.userId}`)
          .then((res) => res.json())
          .then((data) => {
            this.userInfo = data;
      }); }
    }
  };
</script>

一些页面初始化的内容抽出到init()方法,在mounted和beforeRouteUpdate都调用这个方法。代码复用,合理解耦。

vue2.2之前使用watch来监听路由变化:

watch: {
  $route(to, from) {
    // 对路由变化作出响应...
  }
}

4.嵌套路由

在由路由控制的子页面中,部分内容还需要一层路由控制时,可以使用嵌套路由,这时的路由对象可以设置为:

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
        }
      ]
    }
  ]
})

组件(这时的组件其实是显示在另一个“父”<router-view>中的):

<div class="user">
  <h2>User {{ $route.params.id }}</h2>
  <router-view></router-view>
</div>

要注意:

  • 子路径不带/,只有根路由才带;
  • 子路由的其他设置和根路由相同;
  • 可设置空白路由,并将它指到特定组件: { path: "", component: UserHome }

5.重定向和别名

重定向

若是想要在访问根路径时,看到的是网站首页或是在访问空白子路由时,看到第一个子路由中的内容则可以用到路由重定向redirect来代替component

const router = new VueRouter({
  routes: [
    { path: '/', redirect: '/Home' },
    { 
      path: '/user/:id', 
	  component: User,
      children: [
        {
          path: '',
          redirect: 'profile',
        },
        {
          path: 'profile',
          component: UserProfile
        },
        {
          path: 'posts',
          component: UserPosts
        }
      ]
    }
  ]
})

若要将多个路由指向同一组件也可使用重定向

别名

要想使用重定向更“隐蔽”一些(重定向后路由不变,照常显示),可以使用别名alias

const router = new VueRouter({
  routes: [
    { 
      path: '/Home', 
	  component: Home,
      alias: '/' },
    { 
      path: '/user/:id', 
	  component: User,
      children: [
        {
          path: 'profile',
	  	  component: UserProfile,
          alias: '',
        },
        ...
      ]
    }
  ]
})

当别名有多个时,可以设置为数组。
官方实例:https://github.com/vuejs/vue-router/blob/dev/examples/route-alias/app.js

6.链接导航

传统使用<a>标签来进行链接,这样会重新加载整个页面,对于SPA来说并不友好,因此使用<router-link>来代替<a>标签:

<router-link to="/bar">Go to Bar</router-link>

默认情况下会渲染为:

<a href="/bar">Go to Bar</a>

(1)tag属性

若要想让它渲染为<li>,这时需要用到tag属性:

<router-link to="/bar" tag="li">Go to Bar</router-link>

这样会渲染为:

<li>Go to Bar</li>

为了让页面更加友好,使<a>依然存在,可写为:

<router-link to="/bar" tag="li"><a>Go to Bar</a></router-link>

将会渲染为:

<li><a href="/bar">Go to Bar</a></li>

(2)active-class属性

<router-link>组件的to属性中的路径与当前页面的路径相匹配时,生成元素处于激活状态(active)。
这时,vue-router会自动为生成的元素赋予一个class属性,默认为router-link-active,也可以用active-class属性来配置class的属性值。
例如:

<ul>
  <router-link to="/blog" tag="li" active-class="active">
    <a>Blog</a>
  </router-link>
  <router-link to="/user/1234" tag="li" active-class="active">
    <a>User #1234</a>
  </router-link>
</ul>

处于/blog时,渲染为:

<ul>
  <li class="active"><a href="/blog">Blog</a></li>
  <li><a href="/user/1234">User #1234</a></li>
</ul>

(3)原生事件

若要为<router-link>绑定事件需要使用@click.native而非直接@click

    <!-- 这 样 不 行 -->
    <router-link to="/blog" @click="handleClick">Blog</router-link>
    <!-- 这 样 可以 -->
    <router-link to="/blog" @click.native="handleClick">Blog</router-link>

(4)编程式导航

效仿浏览器原生的history方法——如history.pushState()、history.replaceState()以及history.go():

router.push()

to属性调用的就是router.push()

router.replace()

router.push()的区别是:

  • router.push()会向history栈添加一个新的记录——因此,如果用户离开后按下返回键,路由器就会跳转到上一个路由;
  • router.replace()则替换了当前的history记录,所以离开后返回键只能让你回到之前的路由。在发生路由更新时适用。

router.go()

在历史记录中前进和后退,就像按了前进键和后退键。后退一条记录,就用router.go(-1),而前进10条记录,就用router.go(10)。若历史中没那么多条记录,函数的调用就会终止。

  • <router-link>在不同模式下表现也是不同的,在hash模式下,前面的链接会带你前往#/user/1234,而使用history模式的话,则会带你去向/user/1234
  • 默认渲染生成的<a>标签的href属性不仅能使悬停时显示跳转链接,还能使用新窗口来打开,这个vue-router就无法做到了。
  • 无论<router-link>最终被渲染为什么标签,vue都会给它绑定点击监听事件

7.导航守卫

beforeEach在导航发生之前运行,并且可设置取消导航或将用户导航至其他地方。守卫是异步解析执行,此时导航在所有守卫 resolve 完之前一直处于 等待中。

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

const router = new VueRouter({ ... })

router.beforeEach((to, from, next) => {
  // ...
})

from和to分别表示导航从哪里来和到哪里去,而next则是一个回调,在里面你可以让vue-router去处理导航、取消导航、重定向到其他地方或者注册一个错误:

导航守卫 | Vue Router

  • 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 函数在任何给定的导航守卫中都被严格调用一次。它可以出现多于一次,但是只能在所有的逻辑路径都不重叠的情况下,否则钩子永远都不会被解析或报错。

例:

router.beforeEach((to, from, next) => {
  if (to.path.startsWith('/account') && ! userAuthenticated())
    next('/login');
  } else {
    next();
  }
});

除了beforeEach还有router.beforeResolverouter.afterEach

  • router.beforeResolve在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,被调用。
  • router.afterEach没有next参数。虽然不会影响导航,但对于做些诸如设置页面标题的小事,也不乏实用之处:
const router = new VueRouter({
  routes: [
    {
      path: '/blog',
      component: PageBlog,
      meta: {
        title: 'Welcome to my blog! '
      }
    }
  ]
});
router.afterEach((to) => {
  document.title = to.meta.title;
});

还有可在路由表中直接定义的路由独享守卫:

const router = new VueRouter({
  routes: [
    {
      path: '/account',
      component: PageAccount,
      beforeEnter(to, from, next) {
        if (! userAuthenticated()) {
          next('/login');
        } else {
          next();
        }
      }
    }
  ]
});

beforeEnter守卫与beforeEach表现完全一致,只不过是单个路由独有。


beforeEachbeforeResolveafterEach是全局导航守卫,还有三个局部导航守卫,功能基本类似:

  • beforeRouteEnter
  • beforeRouteUpdate (2.2 新增)
  • beforeRouteLeave
const Foo = {
  template: `...`,
  beforeRouteEnter (to, from, next) {
    // 在渲染该组件的对应路由被 confirm 前调用
    // 不!能!获取组件实例 `this`
    // 因为当守卫执行前,组件实例还没被创建
    // 但是,可以在next里传一个回调,该回调会被传入组件实例并作为其第一个参数:
    next((vm) => {
        console.log(vm.$route);
    });
  },
  beforeRouteUpdate (to, from, next) {
    // 在当前路由改变,但是该组件被复用时调用(此时并不会调用mounted)
    // 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
    // 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
    // 可以访问组件实例 `this`
  },
  beforeRouteLeave (to, from, next) {
    // 导航离开该组件的对应路由时调用
    // 可以访问组件实例 `this`
  }
}

由于在beforeRouteUpdate和beforeRouteLeave里都能使用this,就像在组件内的其他大部分地方一样,因此这两个守卫并不支持在next里传入一个回调。

8.路由元信息

路由元信息设置在路由表中路由项的meta属性。改良之前的写法:

const router = new VueRouter({
  routes: [
    {
      path: '/account',
      component: PageAccount,
      meta: {
        requiresAuth: true
      }
    }
  ]
});

router.beforeEach((to, from, next) => {
  if (to.meta.requiresAuth && ! userAuthenticated()) {
    next('/login');
  } else {
    next();
  }
});

可理解为:需要认证,且认证通过。

一个路由匹配到的所有路由记录会暴露为 $route 对象 (还有在导航守卫中的路由对象) 的 $route.matched 数组。因此,我们需要遍历 $route.matched 来检查路由记录中的 meta 字段。

router.beforeEach((to, from, next) => {
    const requiresAuth = to.matched.some((record) => {
    return record.meta.requiresAuth;
  }
  if (requiresAuth && ! userAuthenticated()) {
    next('/login');
  } else {
    next();
  }
});

若是使用子路由,且子路由都需要认证,则在父路由上添加requiresAuth,并使用$route.matched 来检查即可

不需要给所有需要控制的路由添加requiresAuth。使用to.matched只需要给较高一级的路由添加requiresAuth即可,其下的所有子路由不必添加。

9.路由顺序

vue-router在内部通过遍历路由数组的方式来挑选被显示的路由,并选取其中匹配到当前URL的第一个
可以利用这个特点,来渲染一个显示错误页面:

const router = new VueRoute({
  routes: [
    // ...你的其他路由...
    {
      path: '*',
      component: PageNotFound
    }
  ]
});

当其他路由都匹配不到时,就会显示PageNotFound组件。
在使用嵌套路由时,如果没有匹配到子路由,则路由器会继续往下对其父路由之外的路由列表进行搜寻,所以通配符路由返回的都是同样的PageNotFound组件。如果想让子路由的错误页面也能在父组件中显示,则需要在子路由数组中添加该通配符路由:

const router = new VueRoute({
  routes: [
    {
      path: '/settings',
      component: PageSettings,
      children: [
        {
          path: 'profile',
          component: PageSettingsProfile
        },
        {
          path: '*',
          component: PageNotFound
        }
      ]
    },
      {
      path: '*',
      component: PageNotFound
    }
  ]
});

10.路由命名

给路由器的路由加上一个name属性后,可用路由的名称来代替它们的路径:

const router = new VueRouter({
  routes: [
    {
      path: '/',
      name: 'home',
      component: PageHome
    },
    {
      path: '/user/:userId',
      name: 'user',
      component: PageUser
    }
  ]
});

使用时,以下几种方式等同:

<router-link :to="{ name: 'home' }">Return to home</router-link>
<router-link :to="/home">Return to home</router-link>
<router-link :to="{ path: '/home' }">Return to home</router-link>

当然还可以用router.push()来跳转:

router.push({ name: 'home' });

这样当路径需要变动时,就不需要去单独修改使用到的地方了

指定参数可用params:

<router-link :to="{ name: 'user', params: { userId: 1234 }}">
  User #1234
</router-link>

11.完整的导航解析流程

  • 导航被触发。
  • 在失活的组件里调用 beforeRouteLeave 守卫。
  • 调用全局的 beforeEach 守卫。
  • 在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
  • 在路由配置里调用 beforeEnter。
  • 解析异步路由组件。
  • 在被激活的组件里调用 beforeRouteEnter。
  • 调用全局的 beforeResolve 守卫 (2.5+)。
  • 导航被确认。
  • 调用全局的 afterEach 钩子。
  • 触发 DOM 更新。
  • 调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入。

三、开始使用

1.安装 vue-router

# 不是vue-router
vue add router

装好之后,在main.js中多出一个引入:import router from './router',并在根Vue实例新增属性 router

import Vue from 'vue'
import App from './App.vue'
import store from './store'
import router from './router'
...
new Vue({
  store,
  router,
  render: h => h(App)
}).$mount('#app')

这表明应用初始化时,routerstore(vuex)会以属性的形式传到vue实例中。

src根目录下会产生一个文件:router/index.js

import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'

Vue.use(VueRouter)

const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home
  },
  {
    path: '/about',
    name: 'About',
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
  }
]

const router = new VueRouter({
  mode: 'history',
  base: process.env.BASE_URL,
  routes
})

export default router

此文件作为router的配置文件存在,接下来具体分析这个文件。

当然也可以使用标签直接引入在线的 vue-router

2.认识 vue-router

配置文件router/index.js从上往下依次是:

  • 引入importVueVueRouter、页面模板文件
  • Vue.use(VueRouter)
  • json 形式的路由表routes:路径path、名字name、组建模板component
  • 实例化的路由对象router:模式mode、基础路径base、路由表routes

3.使用 vue-router

配置好之后就可以开始使用了,在需要以vue-router为规则显示页面的地方添加标签<router-view/>

。。。待续


使用过程中要特别注意区分 路由表routes,路由表对象route,路由对象router


拓展阅读:

 类似资料: