# Vue Router路由

# 从零开始简单的路由

简单原理:根据url路径改变渲染的模板

  • window.location.pathname 为当前url的路径
  • 利用render函数、计算属性动态改变渲染的模板
  • 注意:前提条件是需要开启http服务,且所有请求都需要指向一个页面。
<!-- 0_从零开始简单的路由.html -->
<body>
  <div id="app">
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  <script>
    const NotFound = { template: '<p>Page not found</p>'}
    const Home = { template: '<p>Home page</p>'}
    const About = { template: '<p>About page</p>'}

    const routes = {
      '/': Home,
      '/about': About
    }

    let app = new Vue({
      el: '#app',
      data: {
        currentRoute: window.location.pathname
      },
      computed: {
        ViewComponent() {
          return routes[this.currentRoute] || NotFound
        }
      },
      render(h) {
        return h(this.ViewComponent)
      }
      // 等价于
      // render: function(h) {
      //   return h(this.ViewComponent)
      // }
    })
  </script>
</body>

# 利用node来开启HTTP服务

  • 1.在 0_从零开始简单的路由.html 目录下,打开控制台
  • 2.npm init 初始化node,安装express
  • 3.创建index.js文件,并写入下面的代码,开启http服务,并将所有请求重定向到一个页面
  • 4.node index.js后,浏览器访问 127.0.0.1:3000 测试,效果类似于vue-router的history模式
    • 访问 http://127.0.0.1:3000 页面显示 Home page
    • 访问 http://127.0.0.1:3000/about 页面显示 About page
    • 访问未知页面 http://127.0.0.1:3000/test 页面显示 Page not found
// index.js
const express = require('express');
const fs=require("fs");
const app = express();

app.use('*', function(req, res) {
  console.log('接收到请求')
  console.log(req.baseUrl)

  res.sendfile('0_从零开始简单的路由.html')
})

app.listen(3000, function() {  
    console.log("server start at 127.0.0.1:3000");
});

# vue-router(官方路由)

对于大多数单页面应用(SPA),都推荐使用官方支持的 vue-router 库。

# 安装

# 直接下载/CDN

https://unpkg.com/vue-router/dist/vue-router.js

使用示例

<!-- 先加载vue,再加载vue-router -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>

# npm安装

npm install vue-router

使用示例

import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)

# 介绍

对于TypeScript用户来说,vue-router@3.0+ 依赖 vue@2.5+,反之亦然。

Vue Router是Vue.js官方的路由管理。和Vue.js核心深度集成。自定义了两个组件RouterView及RouterLink,均使用render函数渲染,包含功能:

  • 嵌套的路由/视图表
  • 模块化、基于组件的路由配置
  • 路由参数、查询、通配符
  • 基于Vue.js过渡系统的视图过渡效果
  • 细粒度的导航控制
  • 带有自动激活的 CSS class的链接
  • HTML5历史模式或hash模式,在IE9中自动降级
  • 自定义的滚动条行为

vue-router 3.1.3版本源码只有 2900 行左右,有时间可以仔细看看

# 基础

# 起步(第一个vue-router页面)

<body>
  <div id="app">
    <h1>Hello App!</h1>
    <p>
      <!-- router-link标签会默认渲染为一个a标签,to属性用来指定链接-->
      <router-link to="/foo">Go to Foo</router-link>
      <router-link to="/bar">Go to Bar</router-link>
    </p>

    <!-- 路由出口,路由匹配到组件将在这里渲染-->
    <router-view></router-view>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  <script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
  <script>

    // 1. 定义 (路由) 组件。
    const Foo = { template: '<p>foo page</p>'}
    const Bar = { template: '<p>bar page</p>'}

    // 2. 定义路由
    const routes = [
      { path: '/foo', component: Foo },
      { path: '/bar', component: Bar }
    ]

    // 3. 创建 router 实例,然后传 `routes` 配置
    const router = new VueRouter({
      routes // 相当于 routes: routes
    })

    let app = new Vue({
      el: '#app',
      router,
    })
    // 也可以这样写
    // const app = new Vue({
    //   router
    // }).$mount('#app')
  </script>
</body>
  • 可以在任何组件内通过 this.$router 访问路由器,也可以通过 this.$route 访问当前路由
  • 使用 this.$router 的原因是我们并不想在每个独立需要封装路由的组件中都导入路由。
  • 注意,当 router-link 对应的路由匹配成功,将自动设置 class 属性值 .router-link-active
// Home.vue
export default {
  computed: {
    username() {
      // 我们很快就会看到 `params` 是什么
      return this.$route.params.username
    }
  },
  methods: {
    goBack() {
      window.history.length > 1 ? this.$router.go(-1) : this.$router.push('/')
    }
  }
}

# 动态路由匹配

动态路径参数(dynamic segment) 可以将某种模式匹配到的所有路由都映射到同一个组件。

  • 路径参数使用 : 标记, 当匹配到路由时,参数值会被设置到 this.$route.params,可以在每个组件中使用
// 下面的例子中 /user/foo 和 /user/bar 都将映射到相同的路由
const User = {
  template: '<div>User {{ $route.params.id }}</div>'
}

const router = new VueRouter({
  routes: [
    // 动态路径参数 以冒号开头
    { path: '/user/:id', component: User }
  ]
})
  • $route.params相关
模式 匹配路径 $route.params
/user/:un /user/guoqzuo { un: 'guoqzuo'}
/user/:un/post/:post_id /user/guoqzuo/post/666 { un: 'guoqzuo', post_id: '123' }
  • $route.query 等其他参数,以下图为例
    • name 路由名称
    • fullPath: "/user/kevin?a=b&k=a"
    • hash: ""
    • name: undefined
    • params: { id: "kevin" }
    • path: "/user/kevin"
    • query: { a: "b", k: "a" }

1_路由route值.png

# 响应路由参数的变化

使用路由参数时(例如从/user/foo导航到/user/bar),原来的组件实例会被复用。这也意味着组件生命周期的钩子函数不会再被调用。 当组件复用时,想对路由参数的变化做出响应,有两种方法:

  • 可以使用watch来监听 $route 对象:
  • 使用 v2.2 中引入的 beforeRouteUpdate 导航守卫

// 组件复用时,对路由参数变化做出响应的方法
const User = {
  template: '...',

  // 方法1:
  watch: {
    '$route' (to, from) {
      // 对路由变化做出响应
      // to, from 都是一个 $route 对象
    }
  },

  // 方法2: 
  beforeRouteUpdate (to, from, next) {
    // react to route changes...
    // don't forget to call next()
    // 如果这里不调用next(),RouterView不会加载新的页面
  }
}
# 捕获所有路由或404
  • 匹配任意路径,使用通配符 (*),使用通配符路由时,需要确保路由的顺序正确(一般放到最后,用于404)
  • 通过 this.$route.params.pathMatch 可以获取匹配到的信息
{
  path: '*' // 匹配所有路径
}
{
  path: '/user-*' // 匹配以 'user-' 开头的任意路径
}

// 给出一个路由 { path: '/user-*' }
this.$router.push('/user-admin')
this.$route.params.pathMatch // 'admin'

// 给出一个路由 { path: '*' }
this.$router.push('/non-existing')
this.$route.params.pathMatch // '/non-existing'
# 高级匹配模式与匹配优先级
  • vue-router 使用 path-to-regexp (opens new window) 作为路径匹配引擎,所以支持很多高级的匹配模式,如:可选的动态路径参数、匹配0个或多个、一个或多个,甚至自定义正则匹配。
  • 谁先定义的,谁的优先级就最高。

# 嵌套路由

嵌套路由指的是 router-view 里面再嵌套 router-view 的情况。

<div id="app">
  <router-view></router-view>  
</div>

<script>
  const User = {
    template: `
      <div class="user">
        <h2> User {{ $route.params.id }} </h2>
        <router-view></router-view>
      </div>
    `
  }
  const UserHome = { template: '<p>UserHome</p>' }
  const UserProfile = { template: '<p>UserProfile</p>' }
  const UserPosts = { template: '<p>UserPosts</p>' }
  const router = new VueRouter({
    routes: [
      {
        path: '/user/:id',
        component: User,
        children: [
          {
            // 当 /user/:id 匹配成功,
            // UserHome 会被渲染在 User 的 <router-view> 中
            path: '',
            component: UserHome
          },
          {
            // 当 /user/:id/profile 匹配成功,
            // UserProfile 会被渲染在 User 的 <router-view> 中
            path: 'profile',
            component: UserProfile
          },
          {
            // 当 /user/:id/posts 匹配成功
            // UserPosts 会被渲染在 User 的 <router-view> 中
            path: 'posts',
            component: UserPosts
          }
        ]
      }
    ]
  })
</script>
  • 以 / 开头的嵌套路径会被当做根路径,可以充分的使用嵌套组件而无须设置嵌套的路径。
// 访问 /testuser 可以直接使用嵌套组件
const router = new VueRouter({
  routes: [
    {
      path: '/user/:id',
      component: User,
      children: [
        {
          path: '/testuser',
          component: TestUser
        }
      ]
    }
  ]
})
  • 如果嵌套的路由没有匹配到,会影响上级路由的显示,上面的例子中,User里的router-view如果没匹配到,整个User都不会显示。

# 编程式的导航

# router.push

router.push(location, onComplete?, onAbort?)

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

想要导航到不同的 URL,则使用 router.push 方法(内部可以使用this.$router.push)。这个方法会向 history 栈添加一个新的记录,当用户点击浏览器后退按钮时,会回到之前的 URL。

// 字符串
router.push('home')

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

// 命名的路由
router.push({ name: 'user', params: { userId: '123' }})

// 带查询参数,变成 /register?plan=private
router.push({ path: 'register', query: { plan: 'private' }})
  • 注意:如果提供了 path,params 会被忽略, 需要提供路由的 name 或手写完整的带有参数的 path
onst userId = '123'
router.push({ name: 'user', params: { userId }}) // -> /user/123
router.push({ path: `/user/${userId}` }) // -> /user/123
// 这里的 params 不生效
router.push({ path: '/user', params: { userId }}) // -> /user
# router.replace

router.replace(location, onComplete?, onAbort?)

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

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

# router.go

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

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

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

// 前进 3 步记录
router.go(3)
# 操作history
  • router的api效仿了 window.history API
router API window.history API
router.push window.history.pushState
router.replace window.history.replaceState
router.go window.history.go
  • Vue Router 的导航方法 (push、 replace、 go) 在各类路由模式 (history、 hash 和 abstract) 下表现一致。

# 命名路由

有时候,通过一个名称来标识一个路由会更方便

const router = new VueRouter({
  routes: [
    {
      path: '/user/:id',
      name: 'user',
      component: User
    }
  ]
})

跳转到命名路由的两种方式

  • router-link,to属性传一个对象
<!-- 把路由导航到 /user/123 -->
<router-link :to="{ name: 'user', params: { userId: 123 }}">User</router-link>
  • router.push
// 把路由导航到 /user/123
router.push({ name: 'user', params: { userId: 123 }})

# 命名视图

如果需要同级展示多个视图,可以使用命名视图, 如果 router-view 没有设置名字,那么默认为 default。

<router-view class="view one"></router-view>
<router-view class="view two" name="a"></router-view>
<router-view class="view three" name="b"></router-view>

同一个路由渲染多个视图就需要多个组件,需要使用components参数

const router = new VueRouter({
  routes: [
    {
      path: '/',
      components: {
        default: Foo,
        a: Bar,
        b: Baz
      }
    }
  ]
})
# 嵌套命名视图

1_2_嵌套命名视图.png

  • Nav 只是一个常规组件。
  • UserSettings 是一个视图组件。
  • UserEmailsSubscriptions、UserProfile、UserProfilePreview 是嵌套的视图组件。
<!-- UserSettings.vue -->
<div>
  <h1>User Settings</h1>
  <NavBar/>
  <router-view/>
  <router-view name="helper"/>
</div>

<script>
{
  path: '/settings',
  // 你也可以在顶级路由就配置命名视图
  component: UserSettings,
  children: [{
    path: 'emails',
    component: UserEmailsSubscriptions
  }, {
    path: 'profile',
    components: {
      default: UserProfile,
      helper: UserProfilePreview
    }
  }]
}
</script>

# 重定向和别名

# 重定向

可以将某个url重定向到另一个url,比如,将 /a 重定向到 /b,在routes数组元素里使用redirect属性即可,redirect属性有三种参数类型:

  • 字符串,直接定向到另一个链接
  • 对象,重定向到一个命名的路由
  • 函数,动态返回重定向目标
const router = new VueRouter({
  routes: [

    // 1.
    { path: '/a', redirect: '/b' },

    // 2.
    { path: '/c', redirect: { name: 'foo' }},

    // 3.
    { path: '/d', redirect: to => {
      // 方法接收 目标路由(to) 作为参数
      // return 重定向的 字符串路径/路径对象
    }}
  ]
})

注意:导航守卫并没有应用在跳转路由上,而仅仅应用在其目标上。上面的例子中,为 /a, /c, /d 路由添加一个 beforeEach 或 beforeLeave 守卫并不会有任何效果。

# 别名

路由访问/b 时,url保持为/b,实际显示的是/a的页面

const router = new VueRouter({
  routes: [
    { path: '/a', component: A, alias: '/b' }
  ]
})

# 路由组件传参

如果组件内部使用了 $route 的相关属性值,组件和路由会形成高耦合,组件只能在特定的url上使用,限制了其灵活性。可以通过路由传参的方式,将组件和路由解耦

// 这里组件里使用了路由的参数,路由和组件高耦合
const User = {
  template: '<p> User {{ $route.params.id }} </p>'
}
const router = new VueRouter({
  routes: [
    { path: '/user/:id', component: User }
  ]
})

// 使用props传参来解耦, 这样可以在任何地方使用该组件
const User = {
  props: ['id'],
  template: '<p> User {{ id }} </p>'
}
const router = new VueRouter({

  // 如果 props 被设置为 true,route.params 将会被设置为组件属性
  routes: [
    { path: '/user/:id', component: User , props: true },

    // 如果包含命名视图,必须为每个视图加上props属性
    {
      path: '/user/:id',
      components: { default: User, sidebar: Sidebar },
      props: { default: true, sidebar: false }
    }
  ]
})
# props的值有三种
  • 布尔模式, 如果 props 被设置为 true,route.params 将会被设置为组件属性
  • 对象模式, 如果 props 是一个对象,它会被按原样设置为组件属性。当 props 是静态的时候有用。
const router = new VueRouter({
  routes: [
    { path: '/promotion/from-newsletter', component: Promotion, props: { newsletterPopup: false } }
  ]
})
  • 函数模式, 可以创建一个函数返回props
// URL /search?q=vue 会将 {query: 'vue'} 作为属性传递给 SearchUser 组件
const router = new VueRouter({
  routes: [
    { path: '/search', component: SearchUser, props: (route) => ({ query: route.query.q }) }
  ]
})

# HTML5 History 模式

vue-router 默认为 hash 模式, 使用hash来模拟完整的url,当URL改变时,页面不会重新加载, 如果不想使用这种模式,可以使用history模式(这种模式充分利用 history.pushState API 来完成 URL 跳转而无须重新加载页面。)

const router = new VueRouter({
  mode: 'history',
  routes: [...]
})
路径 本地File访问-hash http服务 - hash http服务 - history
/ 4_嵌套路由.html#/ http://127.0.0.1:3000/#/ http://127.0.0.1:3000/
/bar 4_嵌套路由.html#/bar http://127.0.0.1:3000/#/bar http://127.0.0.1:3000/bar
/testuser 4_嵌套路由.html#/testuser http://127.0.0.1:3000/#/testuser http://127.0.0.1:3000/testuser
// http服务 配置 node ,所有路由均指向4_嵌套路由.html
const express = require('express');
const fs=require("fs");
const app = express();

app.use('*', function(req, res) {
  console.log('接收到请求')
  console.log(req.baseUrl)

  res.sendfile('4_嵌套路由.html')
})

app.listen(3000, function() {  
    console.log("server start at 127.0.0.1:3000");
});

更多后台配置,参考:后端配置例子 (opens new window)

# 进阶

# 导航守卫

导航表示路由正在发生改变,守卫的意思类似于钩子函数,从一个链接跳转到另一个链接过程中会触发不同的钩子函数,在函数内部可以对跳转进行拦截处理。

  • 路由钩子函数和路由守卫的区别,钩子函数没有next参数,不可以对路由进行拦截。路由守卫函数有next参数,可以对路由进行拦截
# 全局前置守卫 router.beforeEach()
const router = new VueRouter({...})

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

当一个导航触发时,全局前置守卫按照创建顺序调用。守卫是异步解析执行,此时导航在所有守卫 resolve 完之前一直处于 等待中。如果不调用第三个参数resolve这个钩子,那么导航不会完成跳转。 每个守卫方法接收3个参数:

  • to 类型为Route, 将要进入的目标路由对象
  • from 类型为Route, 导航正要离开的路由
  • next 类型为Function,一定要调用该方法来resolve这个钩子, 执行效果依赖该参数
    • next() 进入管道中的下一个钩子,如果全部ok,则会跳转
    • next(false) 中断当前的导航,如果URL改变了,那边URL地址会重置到from路由对应的地址
    • next('/') 或 next({ path: '/' }) 跳到一个不同的地址
    • next(error) (2.4.0+) 如果参数是一个 Error 实例,导航会被终止且该错误会被传递给 router.onError() 注册过的回调。

应用:在每次跳转前检查token信息,如果过期,则跳转到登陆页面

router.beforeEach((to, from, next) => {
  if (window.localstorage.getItem('token')) {
    next();
  } else {
    next('/login');
  }
})
# 全局后置钩子 router.afterEach()

钩子和守卫不同的是,钩子没有next参数,跳转到新的页面后调用。

router.afterEach((to, from)=> {
  // 可以用来将滚动重置,如果一个页面比较长,滚动到某个位置,跳转到另一个页面,滚动默认在上一次停留的位置, 可以通过这个钩子,返回顶部
  window.scrollTo(0, 0)
})
# 全局解析守卫 router.beforeResolve()

2.5.0 新增

和router.beforeEach类似,区别是在导航被确认之前,且所有组件内守卫何异步路由组件被解析之后,解析守卫才被调用,在 router.afterEach() 之前

# 路由独享的守卫 beforeEnter()
// 可以在定义路由时,单独为某个路由设置守卫
const router = new VueRouter({
  routes: [
    {
      path: '/foo',
      component: Foo,
      beforeEnter: (to, from, next) => {
        // ...
      }
    }
  ]
})
# 组件内的守卫 beforeRoute*()

可以在路由组件内直接定义下面的路由守卫

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

    // 可以用来在跳转下一页时确认提示
    const answer = window.confirm('Do you really want to leave? you have unsaved changes!')
    if (answer) {
      next()
    } else {
      next(false)
    }
  }
}
# 完整的导航解析流程
  • 1.导航被触发
  • 2.失活的组件调用组件内的离开守卫 beforeRouteLeave()
  • 3.调用全局的beforeEach() 守卫
  • 4.在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)
  • 5.在路由配置里调用 beforeEnter()
  • 6.解析异步路由组件
  • 7.在激活的组件里调用 beforeRouteEnter()
  • 8.全局调用beforeResolve() 守卫
  • 9.导航被确认
  • 10.调用全局的 afterEach 钩子
  • 11.触发DOM更新
  • 12.用创建好的组件实例,调用组件里 beforeRouteEnter 守卫中传给 next 的回调函数

# 路由元信息

定义路由的时候,可以配置meta字段,用途:在全局导航守卫中检查元字段,看是否需要检查权限

const router = new VueRouter({
  routes: [
    {
      path: '/foo',
      component: Foo,
      children: [
        {
          path: 'bar',
          component: Bar,
          // a meta field
          meta: { requiresAuth: true }
        }
      ]
    }
  ]
})

路由匹配到的路由记录会存放到 $route.matched 数组,通过获取对应的meta字段来进行校验

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

  // .some() 对数组的每一项运行给定函数, 如果函数对数组的任一项返回的true,return true
  if (to.matched.some(record => record.meta.requiresAuth)) {

    // this route requires auth, check if logged in
    // if not, redirect to login page.
    if (!auth.loggedIn()) {
      next({
        path: '/login',
        query: { redirect: to.fullPath }
      })
    } else {
      next()
    }
  } else {
    next() // 确保一定要调用 next()
  }
})

# 过渡效果

RouterView 是基本的动态组件,所以我们可以用 transition 组件给它添加一些过渡效果

<transition>
  <router-view></router-view>
</transition>
# 单个路由的过渡

上面的用法为每个组件都设置了一样的过渡效果,如果想让每个组件有不同的过渡效果,可以在各路由组件内使用 transition 设置不同的name

const Foo = {
  template: `
    <transition name="slide">
      <div class="foo">...</div>
    </transition>
  `
}

const Bar = {
  template: `
    <transition name="fade">
      <div class="bar">...</div>
    </transition>
  `
}
# 基于路由的动态过渡

可以根据当前路由与目标路由的变化关系,动态设置过渡效果,完整示例 (opens new window)

<!-- 使用动态的 transition name -->
<transition :name="transitionName">
  <router-view></router-view>
</transition>

<script>
  // 在父组件内,watch $route,决定使用哪种过渡
  watch: {
    '$route' (to, from) {
      const toDepth = to.path.split('/').length
      const fromDepth = from.path.split('/').length
      this.transitionName = toDepth < fromDepth ? 'slide-right' : 'slide-left'
    }
  }
</script>

# 数据获取

进入某个路由,需要从服务器获取数据,有两种方式实现

  • 导航完成后获取,先完成导航,在接下来的组件生命钩子中获取数据,数据获取期间显示 ‘加载中’之类的提示
  • 导航完成前获取, 导航完成前,在路由进入的守卫中获取数据,数据获取成功后执行导航
# 导航完成后获取数据
<template>
  <div class="post">
    <div v-if="loading" class="loading">
      Loading...
    </div>

    <div v-if="error" class="error">
      {{ error }}
    </div>

    <div v-if="post" class="content">
      <h2>{{ post.title }}</h2>
      <p>{{ post.body }}</p>
    </div>
  </div>
</template>
<script>
export default {
  data () {
    return {
      loading: false,
      post: null,
      error: null
    }
  },
  created () {
    // 组件创建完后获取数据,
    // 此时 data 已经被 observed 了
    this.fetchData()
  },
  watch: {
    // 如果路由有变化,会再次执行该方法
    '$route': 'fetchData'
  },
  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
        }
      })
    }
  }
}
</script>
# 导航完成前获取数据

在导航到新的路由前获取数据。在to的组件的 beforeRouteEnter 守卫中获取数据,当数据获取成功后调用 next 方法。为后一个页面获取数据时,页面会停留在当前页,需要给一个加载的提示,以及如果数据获取错误,给出错误提示

export default {
  data () {
    return {
      post: null,
      error: null
    }
  },
  beforeRouteEnter (to, from, next) {
    getPost(to.params.id, (err, post) => {
      next(vm => vm.setData(err, post))
    })
  },
  // 路由改变前,组件就已经渲染完了
  // 逻辑稍稍不同
  beforeRouteUpdate (to, from, next) {
    this.post = null
    getPost(to.params.id, (err, post) => {
      this.setData(err, post)
      next()
    })
  },
  methods: {
    setData (err, post) {
      if (err) {
        this.error = err.toString()
      } else {
        this.post = post
      }
    }
  }
}

# 滚动行为

当切换到新的路由,想要页面滚到顶部,或者保持原先的滚动位置,就像重新加载页面一样,vue-router支持在路由切换时,定义页面如何滚动 , 该功能只支持在history.pushState的浏览器中可用

创建Router实例时,可以提供scrollBehavior方法,返回值格式:

  • { x: number, y: number }
  • { selector: string, offset? : { x: number, y: number }} (offset 只在 2.6.0+ 支持)
const router = new VueRouter({
  routes: [...],
  scrollBehavior (to, from, savedPosition) {
    // return 期望滚动到哪个的位置

    // 示例1
    return { x: 0, y: 0 }

    // 示例2
    if (savedPosition) {
      return savedPosition
    } else {
      return { x: 0, y: 0 }
    }

    // 示例3 模拟“滚动到锚点”的行为
    if (to.hash) {
      return {
        selector: to.hash
      }
    }
  }
})

使用路由元信息,更细粒度的控制滚动, 完整示例 (opens new window)

# 异步滚动 2.8.0新增

scrollBehavior可以返回一个Promise

scrollBehavior (to, from, savedPosition) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve({ x: 0, y: 0 })
    }, 500)
  })
}

# 路由懒加载

打包构建应用时,js包会变的比较大,影响页面加载。可以把不同的路由对应的组件分割成不同的代码块,当路由被访问时才加载对应的组件。

结合Vue的异步组件 + Webpack的代码分割功能,可以轻松实现路由懒加载

  • 1.将异步组件定义为一个Promise工厂函数
const Foo = () => Promise.resolve({ /* 组件定义对象 */ })
  • 2.在webpack中,使用动态import语法来定义代码分块点
import('./Foo.vue') // 返回Promise

如果使用的是Babel,需要添加 syntax-dynamic-import 插件,Babel 才能正确地解析语法。

结合上面的两者,就是如何定义一个能被Webpack自动代码分割的异步组件

const Foo = () => import('./Foo.vue')
# 使用注释将组件按组分块

如果想把某个路由下的所有组件都打包在同个异步块中,需要特殊的注释语法提供块名称 (Webpack 2.4+)

const Foo = () => import(/* webpackChunkName: "group-foo" */ './Foo.vue')
const Bar = () => import(/* webpackChunkName: "group-foo" */ './Bar.vue')
const Baz = () => import(/* webpackChunkName: "group-foo" */ './Baz.vue')

# TODO

完整API (opens new window) 整理

  • router-link 属性
    • tag 使用 tag 属性指定标签名,依旧会监听点击,触发导航。
    <router-link to="/foo" tag="li">foo</router-link>
    <!-- 渲染结果 -->
    <li>foo</li>
    
    • active-class, 设置链接激活时使用的 CSS 类名。默认值可以通过路由的构造选项 linkActiveClass 来全局配置。默认值为 "router-link-active"

# 整合第三方路由

第三方路由这里暂不讨论,参考 第三方路由 (opens new window)

上次更新: 2020/10/29 22:59:19