# 1. 路由基础

# 1.1 为什么需要路由

以前页面的切换都是通过URL页面地址的切换完成,但是这样切换页面会导致白屏问题,一个页面跳转到一个页面需要等待加载html结构,css资源之类的。由于webApp的流行,就是网页有app的体验,所以出现了单页应用,因此需要路由技术解决。

下面动图就演示了单页应用的好处,页面切换是非常流畅的。

vue卖座网

# 1.2 单页应用

单页应用就是一个页面包含了所有子页面,然后通过条件判断切换不同部分页面显示。他的优势是因为一开始就已经把所有资源加载完毕,所以在页面切换时无需加载资源,就会很流畅。但是他也有劣势,例如首次加载需要等待较旧,还有SEO问题。

因此,单页应用并不是万能药,如果开发的是后台应用和webApp,在不考虑加载时间和希望提升页面切换体验的,可以使用单页模式。

# 1.3 通过判断实现简单页面切换

See the Pen RwRpoGr by 蟹老板 (@zhengguorong) on CodePen.

# 1.4 第一个Vue路由例子

See the Pen YzWVwRB by 蟹老板 (@zhengguorong) on CodePen.

# 创建步骤:

1、定义登录和注册页面组件

const Login = {
template: `
<div id="loginForm">
  <h2>登录页</h2>
  <label for="">用户名</label><br>
  <input type="text"><br>
  <label for="">密码</label><br>
  <input type="password" name="" id=""><br>
  <button>登录</button>
</div>
`,
}

const Register =  {
template: `
<div id="registerForm">
  <h2>注册页</h2>
  <label for="">用户名</label><br>
  <input type="text"><br>
  <label for="">密码</label><br>
  <input type="password" name="" id=""><br>
  <label for="">Email</label><br>
  <input type="email" name="" id=""><br>
  <button>注册</button>
</div>
`,
};

2、定义路由

path:访问路径

component:路径对应显示组件

const routes = [
  { path: '/login', component: Login },
  { path: '/register', component: Register },
]

3、实例化路由,并把它传递给Vue

const router = new VueRouter({
  routes
});

new Vue({
  el: '#app',
  data: {},
  router,
});

# 1.5 获取路由参数

在1.4实例中,完成了最基本的路由切换页面,但是有时候切换页面的时候,我们需要给页面传递一些参数,例如我们希望跳转到注册页面的时候,传递一个注册来源字段source,以前我们给页面传递参数可以通过?source=微信的形式传递,但是在vue-router中,参数的传递有点不一样。

首先,你要在路由配置的时候,使用冒号加变量名声明定义参数。

const routes = [
  { path: '/login', component: Login },
  // 通过冒号作为标记传递参数
  { path: '/register/:source', component: Register },
]

接着,使用router-link添加参数

<router-link to="/register/微信">注册页</router-link>

最后,你就可以通过路由实例的params属性获取参数了

    const Register =  {
      template: `
            <div id="registerForm">
                <h2>注册页</h2>
                <label for="">用户名</label><br>
                <input type="text"><br>
                <label for="">密码</label><br>
                <input type="password" name="" id=""><br>
                <label for="">Email</label><br>
                <input type="email" name="" id=""><br>
                <button>注册</button>
            </div>
            `,
        mounted() {
          // 通过this.$route访问当前路由实例
          // params为路由参数,返回一个参数对象
          console.log(this.$route.params)
        }
    };

# 1.6 响应路由参数的变化

提醒一下,当使用路由参数时,例如从 /user/foo 导航到 /user/bar原来的组件实例会被复用。因为两个路由都渲染同个组件,比起销毁再创建,复用则显得更加高效。不过,这也意味着组件的生命周期钩子不会再被调用

复用组件时,想对路由参数的变化作出响应的话,你可以简单地 watch (监测变化) $route 对象:

const Register = {
  template: '...',
  watch: {
    // to 表示跳转到目标页面的路由对象
    // from 表示跳转前的路由对象
    $route(to, from) {
      // 对路由变化作出响应...
    }
  }
}

或者使用 2.2 中引入的 beforeRouteUpdate 导航守卫

const Register = {
  template: '...',
  beforeRouteUpdate (to, from, next) {

  }
}

# 1.7 捕获所有路由或 404 Not found 路由

当访问一个未定义的路径,可以使用通配符*实现拦截,统一显示页面找不到提示

const routes = [
  { path: '/index', component: Index },
  // *表示通配符,把没有匹配到的路由使用NotFound组件
  { path: '*', component: NotFound },
]

# 1.8 编程方式实现页面跳转

除了使用 <router-link> 创建 a 标签来定义导航链接,我们还可以借助 router 的实例方法,通过编写代码来实现。

声明式 编程式
<router-link :to="..."> router.push(...)
// 字符串
router.push('/register/test')

// 对象
router.push({ path: '/register' })

// 命名的路由, 如果需要使用name跳转,路由必须配置name属性
router.push({ name: 'register', params: { source: '123' }})

// 带查询参数,变成 /register?source=123
router.push({ path: 'register', query: { source: '123' }})

# 1.8.1 router.replace

router.push 很像,唯一的不同就是,它不会向 history 添加新记录,而是跟它的方法名一样 —— 替换掉当前的 history 记录。

声明式 编程式
<router-link :to="..." replace> router.replace(...)

# 1.8.2 router.go(n)

这个方法的参数是一个整数,意思是在 history 记录中向前或者后退多少步,类似 window.history.go(n)

// 在浏览器记录中前进一步,等同于 history.forward()
router.go(1)

// 后退一步记录,等同于 history.back()
router.go(-1)

// 前进 3 步记录
router.go(3)

// 如果 history 记录不够用,那就默默地失败呗
router.go(-100)
router.go(100)

# 1.9 命名视图

有时候想同时 (同级) 展示多个视图,而不是嵌套展示,例如创建一个布局,有 sidebar (侧导航) 和 main (主内容) 两个视图,这个时候命名视图就派上用场了。你可以在界面中拥有多个单独命名的视图,而不是只有一个单独的出口。如果 router-view 没有设置名字,那么默认为 default

See the Pen vYKjjEz by 蟹老板 (@zhengguorong) on CodePen.

<router-view name="header"></router-view>
<router-view name="main"></router-view>
<router-view></router-view>

一个视图使用一个组件渲染,因此对于同个路由,多个视图就需要多个组件。确保正确使用 components 配置 (带上 s):

const Header = {
  template: '<div style="width: 100%; height: 100px; border: 1px solid;">头部</div>'
}

const Main = {
  template: `<div style="width: 100%; height: 300px; border: 1px solid red;">中间区域</div>`
}

const Default = {
  template: `<div style="width: 100%; height: 100px; border: 1px solid blue;">默认路由</div>`
}

const routes = [
  // 注意是components,和以前的指定单个组件不一样
  { path: '/', components: { header: Header, main: Main, default: Default } }
];

# 1.10 重定向

重定向表示当页面访问到某个路径时,再做一次跳转都别的路由。

下面的设置就相当于当访问不存在的页面时,跳转到登录页

const routes = [
  { path: '/login', component: Login },
  { path: '/register', component: Register },
  { path: '*', redirect: '/login' }
]

# 1.11 嵌套路由

实际生活中的应用界面,通常由多层嵌套的组件组合而成。同样地,URL 中各段动态路径也按某种结构对应嵌套的各层组件,例如:

/user/foo/profile                     /user/foo/posts
+------------------+                  +-----------------+
| User             |                  | User            |
| +--------------+ |                  | +-------------+ |
| | Profile      | |  +------------>  | | Posts       | |
| |              | |                  | |             | |
| +--------------+ |                  | +-------------+ |
+------------------+                  +-----------------+

借助 vue-router,使用嵌套路由配置,就可以很简单地表达这种关系。

See the Pen oNLddmm by 蟹老板 (@zhengguorong) on CodePen.

# 2. 路由进阶

# 2.1 导航守卫

正如其名,vue-router 提供的导航守卫主要用来通过跳转或取消的方式守卫导航。有多种机会植入路由导航过程中:全局的, 单个路由独享的, 或者组件级的。

# 2.1.1 全局前置守卫

你可以使用 router.beforeEach 注册一个全局前置守卫:

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

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

当一个导航触发时,全局前置守卫按照创建顺序调用。守卫是异步解析执行,此时导航在所有守卫 resolve 完之前一直处于 等待中

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

  • to: Route: 即将要进入的目标 路由对象
  • from: Route: 当前导航正要离开的路由
  • next: Function: 一定要调用该方法来 resolve 这个钩子。执行效果依赖 next 方法的调用参数。
    • next(): 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed (确认的)。
    • next(false): 中断当前的导航。如果浏览器的 URL 改变了 (可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from 路由对应的地址。
    • next('/') 或者 next({ path: '/' }): 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。你可以向 next 传递任意位置对象,且允许设置诸如 replace: truename: 'home' 之类的选项以及任何用在 router-linkto proprouter.push 中的选项。
    • next(error): (2.4.0+) 如果传入 next 的参数是一个 Error 实例,则导航会被终止且该错误会被传递给 router.onError() 注册过的回调。

下面演示使用导航守卫验证用户是否登录,如果没登录跳转到登录页。

// 使用导航守卫,当用户进入页面前,验证用户是否登录
router.beforeEach((to, from, next) => {
  // 必须调用next方法才能跳转到下个页面
  // next()
  // 还可以给next设置参数,跳转到指定页面,例如下面代码判断用户是否登录,如果没登录,跳转到登录页面
  // 判断未登录,而且目标页面不为登录页面,就跳转
  if (!isLogin && to.path !== '/login') {
    next('/login');
  } else {
    next()
  }
})

# 2.1.2 全局后置钩子

你也可以注册全局后置钩子,然而和守卫不同的是,这些钩子不会接受 next 函数也不会改变导航本身:

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

# 2.1.3 路由独享的守卫

你可以在路由配置上直接定义 beforeEnter 守卫:

const router = new VueRouter({
  routes: [
    {
      path: '/foo',
      component: Foo,
      beforeEnter: (to, from, next) => {
        // ...
      }
    }
  ]
})

这些守卫与全局前置守卫的方法参数是一样的。

# 2.1.4 组件内的守卫

最后,你可以在路由组件内直接定义以下路由导航守卫:

  • beforeRouteEnter
  • beforeRouteUpdate (2.2 新增)
  • beforeRouteLeave
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`
  }
}

# 2.1 路由元信息

如果你希望给路由添加一些额外信息,可以通过meta属性设置。例如下面演示利用meta属性记录用户访问路径。

给路由对象都添加meat属性,属性接收一个对象,对象包含name属性,表示该路由别名。

const routes = [
  { path: '/', redirect: '/blog' },
  {
    path: '/blog',
    component: Blog,
    meta: { name: '博客首页' },
    children: [
      {
        path: 'article',
        component: Article,
        meta: { name: '文章列表' }
      },
      {
        path: 'userInfo',
        component: UserInfo,
        meta: { name: '用户信息' }
      },
    ],
  },
];

通过$route.matched属性可以获取meta信息,当我访问的路径为#/blog/article ,打印$route.matched得到下图的数组,数组对象包含路由信息,也可以通过该对象获取meta字段

meat

# 使用元信息实现面包线案例

See the Pen 路由元信息 by 蟹老板 (@zhengguorong) on CodePen.

# 2.2 过渡动效

以前我们可以通过transition组件给普通元素实现动画,实际上router-view也可以通过transition组件包裹实现动画。

See the Pen 过渡动画 by 蟹老板 (@zhengguorong) on CodePen.