小白学 VUE 系列之Router 路由

介绍

Vue Router 是 Vue.js 官方的路由管理器。它和 Vue.js 的核心深度集成,让构建单页面应用变得易如反掌。

安装

npm install vue-router

语法

链接调用

 <router-link to="/foo">Go to Foo</router-link>

显示内容

 <router-view></router-view>

定义

import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'

Vue.use(VueRouter)

const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home
  },
  {
    path: '/an',
    name: 'AnimateView',
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () => import(/* webpackChunkName: "about" */ '../views/AnimateView.vue')
  }
]

const router = new VueRouter({
  mode: 'history',
  base: process.env.BASE_URL,
  routes
})

export default router
new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')
  • 任何组件内通过 this.$router
  • this.$route 访问当前路由

vue Router 路由规则

  • 一个“路径参数”使用冒号 :标记
const User = {
  template: '<div>User</div>'
}

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

现在访问/user/任意参数即可访问到 User 组件,如果User组件需要获取到这参数,则使用this.$route.params.id即可。

  • 接受参数是this.$router.params
  • 参数不一样的时候,组件都是复用,复用更加高效。例如从 /user/foo 导航到 /user/bar 。这决定了生命周期钩子不会再被调用
    这就意味着我们的所有vue生命周期都不会再次激活,我们可以通过路由监听参数变化来改变页面的数据。

监听路由参数变化写法.
第一种

const User = {
  template: '...',
  watch: {
    $route(to, from) {
      // 对路由变化作出响应...
            //如果改变了。to获得新的路由实例,然后再去调取数据
    }
  }
}

第二种

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

描述

  • 匹配的优先级就按照路由的定义顺序:谁先定义的,谁的优先级就最高
  • 匹配规则支持正则表达式

嵌套路由

一个路由下,包含子路由就是嵌套路由。VueRouter 的参数中使用children 配置

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

<template>
    <div style="padding: 200px">
        子视图下面
        <router-view></router-view><!--必须包含这个,否则无法输出子嵌套-->
    </div>
</template>

嵌套下如果不是写了/开头,则以父组件的路径来进行组合,例如父路径是:/user,子定义是:seting,那么访问这个最后是/user/seting,如果你想完全自定义,那么输入/开头,例如子路径是:/user/seting/seting,那么访问就是这个绝对路径.

如果没有匹配的时候,会空,那么需要定义一个不匹配的子路由

 { path: '', component: UserHome }

js下导航

使用 创建 a 标签来定义导航链接,我们还可以借助 router 的实例方法,通过编写代码来实现.
要导航到不同的 URL,则使用 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 会被忽略

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.push 很像,唯一的不同就是,它不会向 history 添加新记录,而是跟它的方法名一样 —— 替换掉当前的 history 记录。

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

前进后退栈

router.go(n)

命名路由

const router = new VueRouter({
  routes: [
    {
      path: '/user/:userId',
      name: 'user',//这里就是命名路由的名字
      component: User
    }
  ]
})

调用

<router-link :to="{ name: 'user', params: { userId: 123 }}">User</router-link>

JS

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>

视图,例如用于渲染侧边,顶部,中间内容区域

const router = new VueRouter({
  routes: [
    {
      path: '/',
      components: {
        default: Foo,
        a: Bar,
        b: Baz
      }
    }
  ]
})

通样支持子嵌套。

重定向和别名

例子是从 /a 重定向到 /b:

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

可以是一个命名的路由:

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

可以是一个方法,动态返回重定向目标:

const router = new VueRouter({
  routes: [
    { path: '/a', redirect: to => {
      // 方法接收 目标路由 作为参数
      // return 重定向的 字符串路径/路径对象
    }}
  ]
})

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

别名

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

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

HTML5 History 模式

  • vue-router 默认 hash 模式

后端配置例子.
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;
}

路由懒加载

当打包构建应用时,JavaScript 包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了

const Foo = () => import('./Foo.vue')

const router = new VueRouter({
  routes: [
    { path: '/foo', component: Foo }
  ]
})

把组件按组分块

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

全局前置守卫(前置中间件)

全局守卫,可以设置我们访问每个链接,进行拦截,类似中间件这样的做法。
守卫方法接收三个参数:

  • to: Route: 即将要进入的目标 路由对象
  • from: Route: 当前导航正要离开的路由
  • next: Function: 一定要调用该方法来 resolve 这个钩子。执行效果依赖 next 方法的调用参数。
    • next(): 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed (确认的)。
      -- next(false): 中断当前的导航。如果浏览器的 URL 改变了 (可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from 路由对应的地址。
const router = new VueRouter({
  mode: 'history',
  base: process.env.BASE_URL,
  routes
})

router.beforeEach((to, from, next) => {
  console.log('router.beforeEach to',to);
  console.log('router.beforeEach from',from);
  console.log('router.beforeEach next',next);
  return next();
})

全局后置钩子(后置中间件)

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

单个路由内设置守卫

在路由配置上直接定义 beforeEnter 守卫。守卫与全局前置守卫的方法参数是一样的

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

beforeRouteEnter 守卫 不能 访问 this,因为守卫在导航确认前被调用,因此即将登场的新组件还没被创建。不过,你可以通过传一个回调给 next来访问组件实例

beforeRouteEnter (to, from, next) {
  next(vm => {
    // 通过 `vm` 访问组件实例
  })
}
  • beforeRouteUpdate (2.2 新增),在当前路由改变,但是该组件被复用时调用
  • beforeRouteLeave, 导航离开该组件的对应路由时调用

路由元信息

定义路由的时候可以配置 meta 字段,类似路由里面携带参数过来一样。例如判断是否需要登录操作

const router = new VueRouter({
  routes: [
    {
      path: '/foo',
      component: Foo,
      children: [
        {
          path: 'bar',
          component: Bar,
          // a meta field
          meta: { requiresAuth: true }
        }
      ]
    }
  ]
})
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()
  }
})

守卫内to.matched,组件内访问this.$route.matched

获取数据

获取数据有2种形式:

  • 导航完成前获取到数据

导航完成前,在路由进入的守卫中获取数据,在数据获取成功后执行导航。

  • 导航完成后获取数据

先完成导航,然后在接下来的组件生命周期钩子中获取数据。

导航完成前获取到数据

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))//通过next 下的vm设置数据
    })
  },
  // 路由改变前,组件就已经渲染完了
  // 逻辑稍稍不同
  beforeRouteUpdate (to, from, next) {
    this.post = null
    getPost(to.params.id, (err, post) => {
      this.setData(err, post)//这个时候,可以获得this,
      next()
    })
  },
  methods: {
    setData (err, post) {
      if (err) {
        this.error = err.toString()
      } else {
        this.post = post
      }
    }
  }
}

导航完成后获取数据

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

评论区 (0)

没有记录
支持 markdown,图片截图粘贴拖拽都可以自动上传。