Vue Router汇总(摘自官网)

1.vue-router功能介绍

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

2.动态路由配置

常规参数:

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

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

const router = new VueRouter({
  routes: [
    // 动态路径参数 以冒号开头
    { path: '/user/:id', component: User }
  ]
})
模式 匹配路径 $route.params
/user/:username /user/evan { username: 'evan' }
/user/:username/post/:post_id /user/evan/post/123 { username: 'evan', post_id: '123' }
注意:

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

可以使用watch进行路由监听
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()
  }
}
非常规参数

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

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

3.嵌套路由

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
        }
      ]
    }
  ]
})
注意:

/ 开头的嵌套路径会被当作根路径。 这让你充分的使用嵌套组件而无须设置嵌套的路径。 此时,基于上面的配置,当你访问 /user/foo 时, User 的出口是不会渲染任何东西,这是因为没有匹配到合适的子路由。如果你想要渲染点什么,可以提供一个 空的 子路由:

const router = new VueRouter({
  routes: [
    {
      path: '/user/:id', component: User,
      children: [
        // 当 /user/:id 匹配成功,
        // UserHome 会被渲染在 User 的 <router-view> 中
        { path: '', component: UserHome },

        // ...其他子路由
      ]
    }
  ]
})

4.编程式导航

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

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

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

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

声明式 编程式
<router-link :to="..." replace> router.replace(...)
3). 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)
小结:

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

5.命名视图

有时候想同时 (同级) 展示多个视图,而不是嵌套展示,例如创建一个布局,有 sidebar (侧导航) 和 main (主内容) 两个视图,这个时候命名视图就派上用场了。你可以在界面中拥有多个单独命名的视图,而不是只有一个单独的出口。如果 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>
const router = new VueRouter({
  routes: [
    {
      path: '/',
      components: {
        default: Foo,
        a: Bar,
        b: Baz
      }
    }
  ]
})
注意:

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

6.HTML5 History 模式

history模式需要后台配置支持。因为我们的应用是个单页客户端应用,如果后台没有正确的配置,当用户在浏览器直接访问就会返回 404,这就不好看了。要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面

后台配置例子:
Apache
<IfModule mod_rewrite.c>
  RewriteEngine On
  RewriteBase /
  RewriteRule ^index\.html$ - [L]
  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteCond %{REQUEST_FILENAME} !-d
  RewriteRule . /index.html [L]
</IfModule>
nginx
location / {
  try_files $uri $uri/ /index.html;
}
原生 Node.js
const http = require('http')
const fs = require('fs')
const httpPort = 80

http.createServer((req, res) => {
  fs.readFile('index.htm', 'utf-8', (err, content) => {
    if (err) {
      console.log('We cannot open "index.htm" file.')
    }

    res.writeHead(200, {
      'Content-Type': 'text/html; charset=utf-8'
    })

    res.end(content)
  })
}).listen(httpPort, () => {
  console.log('Server listening on: http://localhost:%s', httpPort)
})
警告

给个警告,因为这么做以后,你的服务器就不再返回 404 错误页面,因为对于所有路径都会返回 index.html 文件。为了避免这种情况,你应该在 Vue 应用里面覆盖所有的路由情况,然后在给出一个 404 页面。

const router = new VueRouter({
  mode: 'history',
  routes: [
    { path: '*', component: NotFoundComponent }
  ]
})

7.导航守卫

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

  • 全局前置守卫 beforeEach
const router = new VueRouter({ ... })

router.beforeEach((to, from, next) => {
  // ...
  // 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是**confirmed**(确认的)。
  next();
  // 中断当前的导航。如果浏览器的 URL 改变了 (可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到`from`路由对应的地址。
  next(false): 
  // 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。你可以向`next`传递任意位置对象,且允许设置诸如`replace: true`、`name: 'home'`之类的选项以及任何用在[`router-link`的`to`prop](https://router.vuejs.org/zh/api/#to)或[`router.push`](https://router.vuejs.org/zh/api/#router-push)中的选项。
  next('/')`或者`next({ path: '/' })
  // 如果传入`next`的参数是一个`Error`实例,则导航会被终止且该错误会被传递给[`router.onError()`](https://router.vuejs.org/zh/api/#router-onerror)注册过的回调。
  next(error)
})
  • 全局解析守卫 beforeResolve

在 2.5.0+ 你可以用 router.beforeResolve 注册一个全局守卫。这和 router.beforeEach 类似,区别是在导航被确认之前, 同时在所有组件内守卫和异步路由组件被解析之后 ,解析守卫就被调用。

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

router.beforeResolve((to, from, next) => {
  // ...
  next();
})
  • 全局后置钩子 afterEach
const router = new VueRouter({ ... })
// 后置钩子不接受next参数
router.afterEach((to, from) => {
  // ...
})
  • 路由独享的守卫 beforeEnter
const router = new VueRouter({
  routes: [
    {
      path: '/foo',
      component: Foo,
      beforeEnter: (to, from, next) => {
        // ...
      }
    }
  ]
})
  • 组件内的守卫 beforeRouteEnter , beforeRouteUpdate , 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`
  }
}

8. 完整的导航解析流程

1.  导航被触发。
2.  在失活的组件里调用离开守卫。
3.  调用全局的`beforeEach`守卫。
4.  在重用的组件里调用`beforeRouteUpdate`守卫 (2.2+)。
5.  在路由配置里调用`beforeEnter`。
6.  解析异步路由组件。
7.  在被激活的组件里调用`beforeRouteEnter`。
8.  调用全局的`beforeResolve`守卫 (2.5+)。
9.  导航被确认。
10.  调用全局的`afterEach`钩子。
11.  触发 DOM 更新。
12.  用创建好的实例调用`beforeRouteEnter`守卫中传给`next`的回调函数。

9.过渡动效

<transition name="fade">
  <router-view></router-view>
</transition>
.fade-enter-active, .fade-leave-active { 
     transition: opacity .5s; 
} 
.fade-enter, .fade-leave-to { 
     opacity: 0; 
}

10.数据获取

  • 导航完成后获取数据
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
        }
      })
    }
  }
}
  • 在导航完成前获取数据
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
      }
    }
  }
}

11.滚动行为

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

const router = new VueRouter({
  routes: [...],
  scrollBehavior (to, from, savedPosition) {
    // return 期望滚动到哪个的位置
    // 在按下 后退/前进 按钮时,就会像浏览器的原生表现那样
    if (savedPosition) {
       return savedPosition
    } else {
        return { x: 0, y: 0 }
    }
    // 或启用锚点
    if (to.hash) {
        return {
            selector: to.hash
        }
    }
  }
})

12.路由懒加载

注意: 如果您使用的是 Babel,你将需要添加 syntax-dynamic-import 插件,才能使 Babel 可以正确地解析语法。

结合 Vue 的 异步组件 和 Webpack 的 代码分割功能 ,才实现了路由懒加载
1)异步组件定义为返回一个 Promise 的工厂函数 (该函数返回的 Promise 应该 resolve 组件本身):
const Foo = () => Promise.resolve({ /* 组件定义对象 */ })
2)在 Webpack 2 中,我们可以使用 动态 import 语法来定义代码分块点 (split point):
import('./Foo.vue') // 返回 Promise
将1)和2)合并在一起,实现了路由懒加载,如下:
const Foo = () => import('./Foo.vue')
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐