Vue Router

一,起步

用 Vue.js + Vue Router 创建单页应用,是非常简单的。使用 Vue.js ,我们已经可以通过组合组件来组成应用程序,当你要把 Vue Router 添加进来,我们需要做的是,将组件 (components) 映射到路由 (routes),然后告诉 Vue Router 在哪里渲染它们。下面是个基本例子:


<div id="app">
 <h1>Hello App!</h1>
 <p>
  <!-- 使用 router-link 组件来导航. -->
  <!-- 通过传入 `to` 属性指定链接. -->
  <!-- <router-link> 默认会被渲染成一个 `<a>` 标签 -->
  <router-link to="/foo">Go to Foo</router-link>
  <router-link to="/bar">Go to Bar</router-link>
 </p>
 <!-- 路由出口 -->
 <!-- 路由匹配到的组件将渲染在这里 -->
 <router-view></router-view>
</div>


#JavaScript

0. 如果使用模块化机制编程,导入Vue和VueRouter,要调用 Vue.use(VueRouter)


// 1. 定义 (路由) 组件。

可以从其他文件 import 进来

const Foo = { template: '<div>foo</div>' }
const Bar = { template: '<div>bar</div>' }


// 2. 定义路由

每个路由应该映射一个组件。 其中"component" 可以是通过 Vue.extend() 创建的组件构造器,或者,只是一个组件配置对象。

const routes = [
 { path: '/foo', component: Foo },
 { path: '/bar', component: Bar }
]


// 3. 创建 router 实例,然后传 `routes` 配置

你还可以传别的配置参数, 不过先这么简单着吧。

const router = new VueRouter({
 routes // (缩写) 相当于 routes: routes
})


// 4. 创建和挂载根实例。

记得要通过 router 配置参数注入路由,从而让整个应用都有路由功能

const app = new Vue({
 router
}).$mount('#app')


// 现在,应用已经启动了!

通过注入路由器,我们可以在任何组件内通过 this.$router 访问路由器,也可以通过 this.$route 访问当前路由:

// 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('/')
  }
 }
}

该文档通篇都常使用 router 实例。留意一下 this.$router 和 router 使用起来完全一样。我们使用 this.$router 的原因是我们并不想在每个独立需要封装路由的组件中都导入路由。


要注意,当 <router-link> 对应的路由匹配成功,将自动设置 class 属性值 .router-link-active。




二,动态路由匹配

我们经常需要把某种模式匹配到的所有路由,全都映射到同个组件。例如,我们有一个 User 组件,对于所有 ID 各不相同的用户,都要使用这个组件来渲染。那么,我们可以在 vue-router 的路由路径中使用“动态路径参数”(dynamic segment) 来达到这个效果:

const User = {
 template: '<div>User</div>'
}

const router = new VueRouter({
 routes: [
  // 动态路径参数 以冒号开头
  { path: '/user/:id', component: User }
 ]
})

现在呢,像 /user/foo 和 /user/bar 都将映射到相同的路由。

一个“路径参数”使用冒号 : 标记。当匹配到一个路由时,参数值会被设置到 this.$route.params,可以在每个组件内使用。于是,我们可以更新 User 的模板,输出当前用户的 ID:

const User = {
 template: '<div>User {{ $route.params.id }}</div>'
}


你可以在一个路由中设置多段“路径参数”,对应的值都会设置到 $route.params 中。例如:

模式 匹配路径 $route.params

/user/:username	/user/evan	{ username: 'evan' }
/user/:username/post/:post_id	/user/evan/post/123	{ username: 'evan', post_id: '123' }

除了 $route.params 外,$route 对象还提供了其它有用的信息,例如,$route.query (如果 URL 中有查询参数)、$route.hash 等等。你可以查看 API 文档 的详细说明。


#响应路由参数的变化

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


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

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


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

const User = {
 template: '...',
 beforeRouteUpdate (to, from, next) {
  // react to route changes...
  // don't forget to call next()
 }
}


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

常规参数只会匹配被 / 分隔的 URL 片段中的字符。如果想匹配任意路径,我们可以使用通配符 (*):

{
 // 会匹配所有路径
 path: '*'
}


{
 // 会匹配以 `/user-` 开头的任意路径
 path: '/user-*'
}

当使用通配符路由时,请确保路由的顺序是正确的,也就是说含有通配符的路由应该放在最后。路由 { path: '*' } 通常用于客户端 404 错误。如果你使用了History 模式,请确保正确配置你的服务器。


当使用一个通配符时,$route.params 内会自动添加一个名为 pathMatch 参数。它包含了 URL 通过通配符被匹配的部分:

// 给出一个路由 { 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 作为路径匹配引擎,所以支持很多高级的匹配模式,例如:可选的动态路径参数、匹配零个或多个、一个或多个,甚至是自定义正则匹配。查看它的 文档 学习高阶的路径匹配,还有 这个例子 展示 vue-router 怎么使用这类匹配。


#匹配优先级

有时候,同一个路径可以匹配多个路由,此时,匹配的优先级就按照路由的定义顺序:谁先定义的,谁的优先级就最高。



三,嵌套路由

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

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

要注意,以 / 开头的嵌套路径会被当作根路径。 这让你充分的使用嵌套组件而无须设置嵌套的路径。


你会发现,children 配置就是像 routes 配置一样的路由配置数组,所以呢,你可以嵌套多层路由。


四,编程式的导航

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


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

注意:在 Vue 实例内部,你可以通过 $router 访问路由实例。因此你可以调用

this.$router.push。


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


当你点击 <router-link> 时,这个方法会在内部调用,所以说,点击 <router-link :to="..."> 等同于调用 router.push(...)。


声明式 编程式

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


注意:如果提供了 path,params 会被忽略,上述例子中的 query 并不属于这种情况。取而代之的是下面例子的做法,你需要提供路由的 name 或手写完整的带有参数的 path:


const 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-link 组件的 to 属性。


注意: 如果目的地和当前路由相同,只有参数发生了改变 (比如从一个用户资料到另一个 /users/1 -> /users/2),你需要使用 beforeRouteUpdate 来响应这个变化 (比如抓取用户信息)。


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

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


声明式 编程式

<router-link :to="..." replace> router.replace(...)

#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)


#操作 History

你也许注意到 router.push、 router.replace 和 router.go 跟 window.history.pushState、 window.history.replaceState 和 window.history.go好像, 实际上它们确实是效仿 window.history API 的。


因此,如果你已经熟悉 Browser History APIs,那么在 Vue Router 中操作 history 就是超级简单的。


还有值得提及的,Vue Router 的导航方法 (push、 replace、 go) 在各类路由模式 (history、 hash 和 abstract) 下表现一致。


五,重定向和别名

#重定向

重定向也是通过 routes 配置来完成,下面例子是从 /a 重定向到 /b:

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


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


#别名

“重定向”的意思是,当用户访问 /a时,URL 将会被替换成 /b,然后匹配路由为 /b,那么“别名”又是什么呢?

/a 的别名是 /b,意味着,当用户访问 /b 时,URL 会保持为 /b,但是路由匹配则为 /a,就像用户访问 /a 一样。

上面对应的路由配置为:

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



六,路由组件传参

在组件中使用 $route 会使之与其对应路由形成高度耦合,从而使组件只能在某些特定的 URL 上使用,限制了其灵活性。

使用 props 将组件和路由解耦:


取代与 $route 的耦合

const User = {
 template: '<div>User {{ $route.params.id }}</div>'
}
const router = new VueRouter({
 routes: [
  { path: '/user/:id', component: User }
 ]
})


通过 props 解耦

const User = {
 props: ['id'],
 template: '<div>User {{ id }}</div>'
}
const router = new VueRouter({
 routes: [
  { path: '/user/:id', component: User, props: true },

  // 对于包含命名视图的路由,你必须分别为每个命名视图添加 `props` 选项:
  {
   path: '/user/:id',
   components: { default: User, sidebar: Sidebar },
   props: { default: true, sidebar: false }
  }
 ]
})

这样你便可以在任何地方使用该组件,使得该组件更易于重用和测试。



七,导航守卫


“导航”表示路由正在发生改变。

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


记住参数或查询的改变并不会触发进入/离开的导航守卫。你可以通过观察 $route 对象来应对这些变化,或使用 beforeRouteUpdate 的组件内守卫。


#全局前置守卫

你可以使用 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: true、name: 'home' 之类的选项以及任何用在 router-link 的 to prop 或 router.push 中的选项。


next(error): (2.4.0+) 如果传入 next 的参数是一个 Error 实例,则导航会被终止且该错误会被传递给 router.onError() 注册过的回调。


确保要调用 next 方法,否则钩子就不会被 resolved。


#全局后置钩子

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


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


#路由独享的守卫

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

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

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


#组件内的守卫

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

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`
 }
}

beforeRouteEnter 守卫 不能 访问 this,因为守卫在导航确认前被调用,因此即将登场的新组件还没被创建。


不过,你可以通过传一个回调给 next来访问组件实例。在导航被确认的时候执行回调,并且把组件实例作为回调方法的参数。

beforeRouteEnter (to, from, next) {
 next(vm => {
  // 通过 `vm` 访问组件实例
 })
}

注意 beforeRouteEnter 是支持给 next 传递回调的唯一守卫。对于 beforeRouteUpdate 和 beforeRouteLeave 来说,this 已经可用了,所以不支持传递回调,因为没有必要了。

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


#完整的导航解析流程

导航被触发。

在失活的组件里调用离开守卫。

调用全局的 beforeEach 守卫。

在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。

在路由配置里调用 beforeEnter。

解析异步路由组件。

在被激活的组件里调用 beforeRouteEnter。

调用全局的 beforeResolve 守卫 (2.5+)。

导航被确认。

调用全局的 afterEach 钩子。

触发 DOM 更新。

用创建好的实例调用 beforeRouteEnter 守卫中传给 next 的回调函数。



八,路由元信息

定义路由的时候可以配置 meta 字段:

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

那么如何访问这个 meta 字段呢?


首先,我们称呼 routes 配置中的每个路由对象为 路由记录。路由记录可以是嵌套的,因此,当一个路由匹配成功后,他可能匹配多个路由记录


例如,根据上面的路由配置,/foo/bar 这个 URL 将会匹配父路由记录以及子路由记录。


一个路由匹配到的所有路由记录会暴露为 $route 对象 (还有在导航守卫中的路由对象) 的 $route.matched 数组。因此,我们需要遍历 $route.matched 来检查路由记录中的 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() // 确保一定要调用 next()
 }
})


九,过渡动效

<router-view> 是基本的动态组件,所以我们可以用 <transition> 组件给它添加一些过渡效果:

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

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>
 `
}


#基于路由的动态过渡

还可以基于当前路由与目标路由的变化关系,动态设置过渡效果:


<!-- 使用动态的 transition name -->
<transition :name="transitionName">
 <router-view></router-view>
</transition>
// 接着在父组件内
// 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'
 }
}


十,滚动行为

使用前端路由,当切换到新路由时,想要页面滚到顶部,或者是保持原先的滚动位置,就像重新加载页面那样。 vue-router 能做到,而且更好,它让你可以自定义路由切换时页面如何滚动。


注意: 这个功能只在支持 history.pushState 的浏览器中可用。

当创建一个 Router 实例,你可以提供一个 scrollBehavior 方法:

const router = new VueRouter({
 routes: [...],
 scrollBehavior (to, from, savedPosition) {
  // return 期望滚动到哪个的位置
 }
})

scrollBehavior 方法接收 to 和 from 路由对象。第三个参数 savedPosition 当且仅当 popstate 导航 (通过浏览器的 前进/后退 按钮触发) 时才可用。


这个方法返回滚动位置的对象信息,长这样:

{ x: number, y: number }
{ selector: string, offset? : { x: number, y: number }} (offset 只在 2.6.0+ 支持)

如果返回一个 falsy (译者注:falsy 不是 false,参考这里)的值,或者是一个空对象,那么不会发生滚动。


举例:

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

对于所有路由导航,简单地让页面滚动到顶部。


返回 savedPosition,在按下 后退/前进 按钮时,就会像浏览器的原生表现那样:

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

如果你要模拟“滚动到锚点”的行为:

scrollBehavior (to, from, savedPosition) {
 if (to.hash) {
  return {
   selector: to.hash
  }
 }
}

我们还可以利用路由元信息更细颗粒度地控制滚动。查看完整例子请移步这里。


#异步滚动

你也可以返回一个 Promise 来得出预期的位置描述:

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

将其挂载到从页面级别的过渡组件的事件上,令其滚动行为和页面过渡一起良好运行是可能的。但是考虑到用例的多样性和复杂性,我们仅提供这个原始的接口,以支持不同用户场景的具体实现。


十一,更多

路由懒加载。。。



来源:

https://router.vuejs.org/zh

声明:本站所有文章和图片,如无特殊说明,均为原创发布。商业转载请联系作者获得授权,非商业转载请注明出处。
随机推荐
Node.js dns 模块
JavaScript 使用 qrcode 生成二维码
WordPress用户的user_login, user_nicename, display_name 和 nickname有什么区别
JavaScript history对象
Express 使用 body-parser 处理 HTTP 请求
JavaScript 键盘事件
JavaScript class 的 this 指向
Node.js os 模块