Vue 系列 | Vue-Router

注意: 本文所有示例代码详见:vue-rouer-demo

1.What | 什么是Vue Router

Vue Router是Vue.js提供的官方路由管理器,它和Vue.js深度集成,使构建单页面应用非常方便。

2.Why | 为什么要使用Vue Router

大家打开LorneNote个网站,这是我的一个blog网站,采用传统的开发模式,鼠标右击,在出现的菜单里选择View Page Source 查看资源文件,大家会看到它对应的是一个HTML文件,然后你回到网站里点击归档栏,再次右击查看源文件,你会看到整个页面被重新加载,对应归档的HTML文件,即:

  • https://lornenote.com -----> LorneNote HTML文件
  • https://lornenote.com/archives/ -----> 归档 | LorneNote HTML文件

也就是说每一个URL对应一个HTML文件,这样每次切换页面的时候我们都需要重新加载我们的页面,非常影响用户体验。

然后就诞生了单页面形式SPA(single page applications)。在单页面模式下,不管我们访问什么页面,都返回index.html,类似这样:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <link rel="icon" href="/favicon.ico">
    <title>hello-world</title>
  <link href="/app.js" rel="preload" as="script"></head>
  <body>
    <noscript>
      <strong>We're sorry but hello-world doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  <script type="text/javascript" src="/app.js"></script></body>
</html>

在单文件模式里,我们依然可以跳到不同的HTML文件,但我们一般不会这样做,一个index.html就满足了我们的需求,用户切换URL的时候,不再是重新加载我们的页面,而是根据URL的变化,执行相应的逻辑,数据会通过接口的形式返回给我们,而后做页面更新。Vue Router就是为了解决这样的事情,它可以做这些事情:

  • 提供URL和组件之间的映射关系,并在URL变化的时候执行对象逻辑
  • 提供多种方式改变URL的API(URL的变化不会导致浏览器刷新)

3.How | Vue Router 如何使用

核心使用步骤

  1. 安装并导入VueRouter,调用Vue.use(VueRouter),这样的话可以使用<router-view><router-link>等全局组件
// 安装命令
$ npm install vue-router

// main.js
import App from './App.vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
  1. 定义路由组件入口
// App.vue
<template>
  <div id="app">
    <img alt="Vue logo" src="./assets/logo.png">
    <router-view></router-view>
  </div>
</template>
  1. 使用定义好的路由组件配置路由列表
// routes.js
import HelloWorld from './components/HelloWorld'
const routes = [
    {path: '/', component: HelloWorld, name:'home'}
]

export default routes
  1. 创建路由实例,并通过路由列表初始化路由实例
// main.js
import routes from'./routes'
const router = new VueRouter({
  routes
})
  1. 将创建好的路由实例挂载到跟实例,这样我们就可以使用this.$route全局钩子来操作路由管理器为我们提供的一些属性。
// main.js
new Vue({
  router,
  render: h => h(App),
}).$mount('#app')

注意:
this.$router代表我们刚刚实例化的全局路由实例,它和我们导入router相同,只是为了使用方便,不用在组件中导入了。
this.$route,写法上少了一个r,代表任何组件的当前路由。

路由动态匹配

有时候我们需要映射不同的路由给相同的组件

// routes.js
{ path:'/user/:id', component:User}

// HelloWorld.vue
<router-link to="/user/123">张三-single params</router-link><br>

在User组件中,可以以this.$route.params的形式接收参数id

<template>
    <div>User
        {{ this.$route.params.id }}  // 123
    </div>
</template>

动态匹配是以冒号区隔的,我们可以有多个动态段:

// routes.js
{ path:'/user/:userName/userId/:id', component:User},  

// HelloWorld.vue
<router-link to="/user/王五/userId/789">王五-mutiple params</router-link>

// User.vue
<template>
    <div>User
        {{ this.$route.params }}  //  { "userName": "王五", "id": "789" }
    </div>
</template>

路由routes与path、$route.params三者的关系见下表:

路由模式 匹配path $route.params
/user/:userName/userId/:id /user/王五/userId/789 { “userName”: “王五”, “id”: “789” }

除了$route.parmas,$route还封装了其他的信息如:
$route.query:用于获取URL中的查询,
$route.hash:用于获取url中#之后的信息(自己写的路径带#,值会包含#,如果是history模式下自动追加的#,则值不包含这个#),如果没有,值为空,如果URL中有多个#,从最后一个开始。

我们重写一下上文的例子:

// HelloWorld.vue
      <router-link to="/user/Query/userId/101?code=123">Query</router-link><br>
      <router-link to="/user/Hash/userId/102#hash=123">Hash</router-link><br>

// User.vue
<template>
    <div>User
        {{ this.$route.query }}  // { "code": "123" }
        {{ this.$route.hash }}  // #hash=123  原路径:http://localhost:8081/#/user/Hash/userId/102#hash=123
    </div>
</template>

由于vue-router使用 path-to-regexp作为路径path的匹配引用,所以我们可以使用正则表达式作为路径:

// routes.js
{ path:'/icon-:flower(\\d+).png', component:User},

// HelloWorld.vue
 <router-link to="/icon-flower123">正则匹配</router-link><br>

// User.vue
<template>
    <div>User
        {{ this.$route.hash }}  // /icon-flower123  原路径:http://localhost:8081/#/icon-flower123
    </div>
</template>

我们可以使用星号(*)来代表通配符,只有*则匹配所有,常代表404页面,因为路由的优先级是优先配置优先匹配,相同的组件谁在前面先匹配谁。所以404页面通常写在最后一个:

// routes.js
const routes = [
  // ...
  { path:'/user-*', component:User},
  { path:'*', component:User}
]

// HelloWorld.vue
<router-link to="/user-admin">*Admin</router-link><br>
<router-link to="/none">404</router-link><br>

// User.vue--*Admin
<template>
    <div>User
      {{ this.$route.params.pathMatch }} // admin
    </div>
</template>

// User.vue--404
<template>
    <div>User
      {{ this.$route.params.pathMatch }} // /none
    </div>
</template>

如上:
当使用*号路由时,一个参数pathMatch会自动追加给$route.params,它匹配星号后面的部分。

嵌套路由

真实的应用中常常用到组件之间的嵌套,使用vue-router可以很简单的表达这种嵌套关系,我们可以在子组件中嵌套使用<router-view>,如:

// NestedRoutes.vue
<template>
    <div>
        <a href="/">←返回</a><br>
        <router-link to="/nestedRoutes/profile">profile</router-link><br>
        <router-link to="/nestedRoutes/archive">archive</router-link><br>
        <p>NestedRoutes</p>
        <router-view></router-view>
    </div>
</template>

为了做嵌套渲染,我们使用children操作符在路由中做结构配置:

// routes.js 嵌套路由
  {path:'/nestedroutes', component: NestedRoutes, name: 'nestedroutes',
   children: [
     {path:'profile', component: resolve => require(['./components/nested-routes/Profile'],resolve)}, // 匹配  /nestedroutes/profile
     {path:'archive', component: resolve => require(['./components/nested-routes/Archive'],resolve)}   // 匹配  /nestedroutes/archive
   ]
  },

嵌套路由以/为根路径,拼接路径的剩余部分,children其实和routes的最外层一样,是数组,所以我们可以根据需要继续嵌套。

当我们访问/nestedroutes的时候,不会在这里渲染任何内容,因为没有子路由,如果我们想要渲染,可以在路由里配置一个空的path的子路由:

// 嵌套路由
  {path:'/nestedroutes', component: NestedRoutes, name: 'nestedroutes',
   children: [
     {path:'', component: resolve => require(['./components/nested-routes/NestedRoutesHome'],resolve)},
     {path:'profile', component: resolve => require(['./components/nested-routes/Profile'],resolve)},
     {path:'archive', component: resolve => require(['./components/nested-routes/Archive'],resolve)}
   ]
  },

编程式的导航

除了以<router-link>的标签形式导航之外,我们还可以使用编程的方式导航,vue-router为我们提供了对应的实例方法this.$router.push

这个方法会将URL页面推到历史堆栈,所以当我们点击浏览器的返回按钮的时候指向前一个URL。当我们点击<router-link>的时候,内部其实调用的是router.push(...),所以这两个形式功能是等同的。

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

location:导航位置,是一个字符串path或位置描述符对象,
第二个和第三个为可选参数,导航执行完成会执行这两个参数。
onComplete导航执行完会执行这个参数(所有的异步钩子都被执行完之后)
使用方式如下:

       <!--path-->
      <button @click="$router.push('programmatic-navigation')">Path</button><br>
       <!--object-->
      <button @click="$router.push({path:'programmatic-navigation'})">Object</button><br>
      <!-- named route-->
      <button @click="$router.push({name:'programmatic-navigation', params: { id:123 }})">NamedRoute</button><br>
       <!--with query  reuslting in /programmatic-navigation?id=123-->
      <button @click="$router.push({path:'programmatic-navigation', query:{ id:123 }})">Query</button><br>

注意:
如果使用了pathparams就被忽略了,但上面的query不会被忽略。所以如果我们要传递参数,可以用两种方式,一种是提供一个name路由,一种是在path里面手动拼接参数:

// routes.js
{ path:'/programmatic-navigation/params=/:id', component: resolve => require(['./components/programmatic-navigation/ProgrammaticNavigation'],resolve), name: 'programmatic-navigation-params' },

// ProgrammaticNavigation.vue
<!--传递参数  返回 /programmatic-navigation/params=/123-->
      <button @click="$router.push({ name:'programmatic-navigation-params', params: { id }})">WidthParmas01</button><br>
      <button @click="$router.push({ path:`/programmatic-navigation/params=/${ id }`})">WidthParmas02</button><br>

这个规则也同样适用于router-linkto属性。

如果我们的当前路径和跳转路径相同,那么我们需要在beforeRouteUpdate方法中响应数据更新:

ProgrammaticNavigation.vue
<template>
    <div>

        <a href="/">←返回</a><br>
        <p>ProgrammaticNavigation</p>
        <pre>{{ JSON.stringify(params) }}</pre>

        <button @click="$router.push({ path:`/programmatic-navigation/params=/${ 456 }`})">WidthParmas03</button><br>
    </div>
</template>

<script>

  export default {
    name: "ProgrammaticNavigation",
    data() {
      return {
        params:this.$route.params
      }
    },
    beforeRouteUpdate (to, from, next) {
      if (to.path == '/programmatic-navigation/params=/456') {
        this.params = to.params
      }
    }
  }
</script>

router.replace
router.replacerouter.push类似,唯一不同的是它不是推送到一个新的历史页,而是替换当前页。

router.go(n)
这个方法和window.history.go(n)类似,n用整数表示,表示在历史页中向前后向后走多少页。

// 后退一页
router.go(-1)
// 前进一页
router.go(1)
// 如果没有,导航失败,页面保持不变
router.go(10)

操作History
路由的router.push,router,replacerouter.go对应window.history.pushState, window.history.replaceStatewindow.history.go, 他们模仿的是window.historyAPIs。

Vue Router的导航方法(push, replace, go)在所有的路由模式下都可以工作(historyhashabstract)。

命名路由

有时候给路由一个名字更方便,用法也很简单:

// routes.js
  {
    path:'/named-routes/:id',
    component: resolve => require(['./components/named-routes/NamedRoutes'],resolve),
    name:'named-routes'
  },

// HelloWorld.vue
<router-link :to="{name: 'named-routes', params: { id:123 }}">named-routes router-link</router-link><br>
<button @click="$router.push({name: 'named-routes', params: { id:123 }})">named-routes router.push</button>

这两种方式都是对象传递,注意to前面要加冒号的,表示内部是对象表达式而不是单纯的字符串。

###命名视图
有时候我们可能需要在同一页展示多个视图,而不是做视图嵌套,比如说主页面或者是sidebar侧边栏。这个时候我们就会用到命名视图,也就是说在同一页面下使用多个<router-view>,给每个<router-view>一个不同的名字,如果没给名字,默认名为default

  // routes.js 命名View
  {
    path:'/named-views',
    component: resolve => require(['./components/named-views/NamedViews'],resolve),
    children: [
      {
        path:'',
        components: {
          default:resolve => require(['./components/named-views/ViewA'],resolve),
          b: resolve => require(['./components/named-views/ViewB'],resolve),
          c: resolve => require(['./components/named-views/ViewC'],resolve),
        },
      }
    ]
  },

// NamedViews.vue
<template>
    <div>
        <a href="/">←返回</a><br>
        <p>NamedViews</p>
        <router-view></router-view>
        <router-view name="b"></router-view>
        <router-view name="c"></router-view>
    </div>
</template>

嵌套命名视图
我们可以在嵌套视图里使用命名视图创造更复杂的布局。我们来看一个例子:

// NestedNamedViews.vue
<template>
    <div>
        <p>NestedNamedViews</p>
        <ViewNav/>
        <router-view></router-view>
        <router-view name="b"></router-view>
    </div>
</template>

这里:

  • NestedNamedViews本身是View组件
  • ViewNav是一个常规的组件
  • router-view内部是被嵌套的视图组件

路由配置这样来实现:

// routes.js
{ path: '/nested-named-views',
    component: resolve => require(['./components/named-views/NestedNamedViews'], resolve),
    children: [
      {
        path:'profile',
        component: resolve => require(['./components/nested-routes/Profile'],resolve)
      },
      {
        path:'archive',
        components: {
          default:resolve => require(['./components/named-views/ViewA'],resolve),
          b: resolve => require(['./components/named-views/ViewB'],resolve),
        }
      }
    ]
  },

转发和别名

转发是我们可以在访问a的时候跳转b。
支持路径和命名访问两种形式:

// routes.js
  {
    path:'/orignal01' ,redirect: '/forward'
  },
  {
    path:'/orignal02' ,redirect: {name: 'forward'}
  },

别名是这样的,如果/b是组件a的别名,那意味着我们访问/b的时候,匹配的组件依然是a

// routes.js
{
    path: '/forward',
    name: 'forward',
    component: resolve => require(['./components/redirect-views/RedirectViews'], resolve),
    alias: '/alias'
  },

传递Props给路由组件

为了使路由的组件更加灵活,vue支持在路由组件中传递Props,使用props操作符。支持三种模式:
布尔值模式
props设置为true的时候,route.params将被设置作为组件的props。

// routes.js
{
    path: '/props-to-route/:id',  // http://10.221.40.28:8080/props-to-route/123
    component: resolve => require(['./components/props-route/PropsRoute'], resolve),
    props: true
  },

对象模式
当props是一个对象的时候,这个对象也一样会被设置给组件的props,这在props为固定值的时候很有用:

// routes.js
  {
    path: '/static',  // http://10.221.40.28:8080/static
    component: resolve => require(['./components/props-route/PropsRoute'], resolve),
    props: {name: 'static'}
  },

函数模式
我们可以创建一个函数返回props,这可以让你转化参数为其他类型,经静态值与基于路由的值结合,等等。

// routes.js
  {
    path: '/function-mode', // http://10.221.40.28:8080/function-mode?keyword=%27beijing%27
    component: resolve => require(['./components/props-route/PropsRoute'], resolve),
    props: (route) => ({ query: route.query.keyword})
  },

如果URL为/function-mode?keyword='beijing'将传递{query: 'beijing'}作为组件的props。

注意:
props函数是无状态的,仅仅计算路由的改变。如果需要状态去定义props,那么Vue官方建议封装一个组件,这样vue就能够对状态做出反应。

路由进阶

导航警卫

这是vue-router提供的一些控制路由进程的函数,有三种表现方式:全局定义,路由内定义和组件中定义。

全局定义
分为前置警卫、解析警卫和后置钩子。我们依次看一下:

1.前置警卫

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

无论哪个导航先触发之前都会先调用它,警卫的解析可能是异步的,所以在所有的钩子解析完成之前,导航处于悬停状态(pending)。实践中经常在这里判断是否携带了进入页面的必要信息,否则做跳转。(比如通过URL地址栏手动输入地址非法进入子页面,需要跳转到登录页让用户登录)

注意:别忘了写next函数,否则钩子函数将不会被解析。

2.全局解析警卫

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

和前置警卫一样,只不过是在导航确认之前,所有组件内警卫和异步路由组件被解析之后会立即调用。

3.全局后置钩子
这些钩子和警卫不同的是没有next函数,并且不影响导航。

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

路由独享的警卫
我们可以在路由的配置对象里配置:beforeEnter

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

这个和全局前置警卫作用相同。

组件内警卫
在组件内可以定义一些路由导航警卫(会被传递给路由配置表):

  • beforeRouteEnter
  • beforeRouteUpdate
  • beforeRouteLeave

beforeRouteEnter不能访问this,因为组件还没有被创建。然后我们可以在next回调里访问,在导航被确认时,组件实例会被当做参数传递给这个回调:

beforeRouteEnter (to, from, next) {
  next(vm => {
    // access to component instance via `vm`
  })
}

而在beforeRouteUpdatebeforeRouteLeavethis已经可用。所以next回调没有必要因此也就不支持了。

beforeRouteUpdate (to, from, next) {
  // just use `this`
  this.name = to.params.name
  next()
}

离开警卫常常用于阻止用户意外离开未经保存的内容,导航可用通过next(false)取消。

beforeRouteLeave (to, from, next) {
  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
  5. 在路由配置中调用beforeEnter
  6. 解析异步路由组件
  7. 在激活组件中调用beforeRouterEnter
  8. 调用全局beforeResolve警卫
  9. 导航被确认
  10. 调用全局afterEach钩子
  11. DOM更新被触发
  12. 用创建好的实例调用beforeRouteEnter守卫中传给next的回调函数。

路由Meta字段

定义路由的时候可以包含一个meta字段:

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

怎么样使用这个字段呢?

首先,在routes配置里的每一个路由对象称为路由记录。路由可能会被嵌套,因此当一个路由被匹配的时候,可能会匹配超过一个记录。

例如,上面的路由配置,/foo/bar将匹配父路由记录和子路由记录两个路由配置。

所有的被匹配的路由记录都被导出在$route对象(也在导航警卫的路由对象里)作为$route.matched数组。因此我们需要迭代这个数组来找到路由记录里的meta字段。

在全局导航警卫中检查meta字段的一个例子:

router.beforeEach((to, from, next) => {
  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() // make sure to always call next()!
  }
})

过渡

简单说就是给路由加一些过渡效果。有几种方式可以实现:
1.在统一的router-view入口给一个一致的过渡

<transition>
  <router-view></router-view>
</transition>

查看所有的过渡APIs

2.在每一个组件内部给一个特别的过渡

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

const Bar = {
  template: `
    <transition name="fade">
      <div class="bar">...</div>
    </transition>
  `
}

3.还可以根据当前路由和目标路由之间的关系设计动态过渡

<template>
    <div>
        <div>
            <router-link to="subviewa">subviewa</router-link><br>
            <router-link to="subview-b">subview-b</router-link><br>
            <p>Home</p>
            <transition :name="transitionName">
                <router-view></router-view>
            </transition>
        </div>
    </div>
</template>

<script>
  export default {
    name: "Home",
    data() {
      return {
        transitionName: 'slide-left'
      }
    },
    beforeRouteUpdate (to, from, next) {
      this.transitionName = to.path < from.path ? 'slide-right' : 'slide-left'
      next()
    },
  }
</script>

<style scoped>
    .slide-left-enter, .slide-right-leave-active {
        opacity: 0;
        transform: translateX(10px);
    }
    .slide-left-leave-active, .slide-right-enter {
        opacity: 0;
        transition: all .8s cubic-bezier(1.0, 0.5, 0.8, 1.0);
    }
</style>

数据获取

在实践中,很多时候在进入一个新的路由页面时,我们都是需要从服务器请求数据的,有两种方式:

  • 在导航之后获取:首先导航到新的页面,在导航的生命周期钩子中(比如created方法)做数据获取。
  • 在导航之前获取:在路由导航进入警卫之前获取数据,数据获取完成之后执行导航。

技术上都可以实现,用哪种取决于用户体验的目标。

导航之后获取数据
使用这种方式,我们会立即导航到新的页面,渲染组件,在组件的created钩子中渲染组件。当获取数据是我们可以展示一个loading的状态,并且每一页可以有自己的loading视图。

我们看一个代办事项的例子:

<template>
    <div>
        <div v-if="loading">
            Loading
        </div>
        <div v-if="error">
            {{ error }}
        </div>
        <div v-if="list" >
            <div v-for=" item in list" :key="item.id">
                <p>{{ item.title }}</p>
            </div>
        </div>
    </div>
</template>

<script>
import  TodoAPI  from '../api/todo';
    export default {
        name: "TodoList",
        data () {
            return {
                loading: false,
                list: null,
                error: null
            }
        },
        created () {
            // 组件创建时获取数据
            this.fetchData()
        },
        watch: {
            // 路由改变的时候再次调用方法
            '$route': 'fetchData'
        },
        methods: {
            fetchData () {
                this.error = this.list = null
                // 模拟网络请求
                TodoAPI.getTodoList(
                    (todolist) => {
                    this.loading = false,
                    this.list = todolist
                },
                (error)=>{
                    this.loading = false,
                    this.error = error
                })
            }
        }
    }
</script>

// 网络请求API -- todo.js
const _todoList = [
    {"id": 1, "title": "购物"},
    {"id": 2, "title": "回复邮件" }
]

export default ({
    getTodoList (cb,errorCb) {
        setTimeout(() => {
            Math.random() > 0.5
            ? cb(_todoList)
            : errorCb("数据请求失败")
        },1000)
    }
})

滚动行为

当我们使用vue-router的时候,我们可能想要打开新页在某一个位置,或使返回历史页保持在上次浏览的位置。vue-router允许我们自定义导航行为。

注意
这个功能仅仅在浏览器支持history.pushState的时候可以用。

当我们创建一个路由实例的时候,我们可以创建一个scrollBehavior函数。

const scrollBehavior = function (to, from, savedPosition) {
  if (savedPosition) {
    return savedPosition
  } else {
    const postion = {}

    if (to.hash) {
      postion.selector = to.hash
      if (to.hash === '#anchor2') {
        postion.offset = {y: 100 }
      }
      if (/^#\d/.test(to.hash) || document.querySelector(to.hash)) {
        return postion
      }
      return false
    }

    return new Promise(resolve => {
      if (to.matched.some(m => m.meta.scrollToTop)) {
        postion.x = 0
        postion.y = 0
      }

      this.app.$root.$once('triggerScroll', () => {
        resolve(postion)
      })
    })
  }
}

const router = new  VueRouter({
  mode: 'history',
  scrollBehavior,
  routes,
})

这里面接收to,from两个路由对象,第三个参数savedPosition,第三个参数仅仅在popstate导航中可用(浏览器的向前/后退按钮被触发的时候)。

这个对象返回滚动位置对象,格式如:

  • {x: nubmer, y:number }
  • {selector: string, offset? : { x: number, y: number}}(offset 仅仅被支持在2.6.0+以上)

如果是空值或无效值,则不会发出滚动。

scrollBehavior (to, from, savedPosition) {
  if (savedPosition) {
    return savedPosition
  } else {
    return { x: 0, y: 0 }
  }
}

如果返回savedPosition,和我们使用浏览器的向前向后按钮一下,如果有历史滚动页,会返回到历史页查看的位置,如果返回{ x: 0, y: 0 }则滚动到顶部。

在开始的例子里,我们可以这样模拟滚动到某处的行为:

if (to.hash) {
      postion.selector = to.hash

      if (to.hash === '#anchor2') {
        postion.offset = {y: 100 }
      }

     // ...
    }

以下我们返回了一个Promise,来返回滚动的位置

return new Promise(resolve => {
// 如果匹配scrollToTop,我们就返回顶部
      if (to.matched.some(m => m.meta.scrollToTop)) {
        postion.x = 0
        postion.y = 0
      }

      // 如果空值或无效值,返回当前滚动位置
      this.app.$root.$once('triggerScroll', () => {
        resolve(postion)
      })
    })

懒加载路由

当使用一个捆绑器构建应用时,JavaScript包会变得非常大,因此影响页面加载的时间。如果我们可以把每一个路由组件分离单独的块,等浏览页面时再加载他们会效率比较高。

Vue的异步组件功能webpack的代码分离功能可以很容易的做到这一点。

首先,可以将异步组件定义为返回Promise的工厂函数(应解析为组件本身):

const Foo = () => Promise.resolve({ /* component definition */ })

其次,在webpack2,我们可以使用动态导入语法去表示代码分离点:

import('./Foo.vue') // returns a Promise

注意
如果你使用Babel,你需要添加syntax-dynamic-import以便Babel可以正确解析。

把这两个步骤联合起来,我们就可以通过webpack自定定义一个异步的代码分离组件,所以我们可以把我们的路由配置修改为:

{
    path: '/lazy-route', component: () => import('./components/lazy-route/LazyHome')
  }

在同一个块中分组组件

有时,我们想要把所有组件分组到相同的异步块中,为了实现这个,在webpack > 2.4中,我们可以使用特殊的注释语法提供命名块:

{
    path: '/lazyTwo', component: () => import(/* webpackChunkName: "lazy" */ './components/lazy-route/LazyTwo'),
    children: [
      {path: 'lazyThree', component: () => import (/* webpackChunkName: "lazy" */ './components/lazy-route/LazyThree')}
    ]
  },

webpack将使用相同块名称的任何异步模块分组到相同的异步块中。


参考:Vue Router

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章