Vue-router的使用和一些技巧

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

1、简单配置

首先在入口 main.js 文件中引入路由

//使用模块化机制编程,导入Vue和VueRouter,要调用 Vue.use(VueRouter)
import Vue from 'vue';
import VueRouter from 'vue-router';
import App from './App.vue';

Vue.use(Router)  //Vue全局使用Router

// 1. 定义 (路由) 组件,可以从其他文件 import 进来
import Home from './components/Home.vue'
import About from './components/About.vue'

// 2. 定义路由
// 每个路由应该映射一个组件。 其中"component" 可以是通过 Vue.extend() 创建的
// 组件构造器,或者只是一个组件配置对象。
const routes = [
	// 配置路由,因为会有多个,所以是个数组
    {
        path: '/',
        redirect: '/home' // 如果是根路径,则重定向到 /home
    },
    // 可以通过 链接路径 或者 路由别名 链接到一个路由
    {
        path: '/home', // 连接路径 <router-link to="/home">Home</router-link>
        name: 'home', // 给路由设置名称,router-link 的 to 属性传一个对象,<router-link :to="{ name: 'home'}">Home</router-link>
        component: Home //  对应的组件模板
    },
    {
        path: '/about',
        name: 'about',
        component: About
    }
]

// 3. 创建 router 实例,然后传 `routes` 配置
// 你还可以传别的配置参数, 不过先这么简单着吧。
const router = new VueRouter({
  routes // (缩写) 相当于 routes: routes
})

// 4. 创建和挂载根实例。
// 记得要通过 router 配置参数注入路由,
// 从而让整个应用都有路由功能
new Vue({
    router,
    render: h => h(App)
}).$mount('#app')

然后在 App.vue 中设置导航和路由出口

<template>
    <div>
        <!-- 使用 router-link 组件来导航. -->
        <!-- 通过传入 `to` 属性指定链接. -->
        <!-- <router-link> 默认会被渲染成一个 `<a>` 标签 -->
        <router-link to="/home">Home</router-link>
        <router-link to="/about">About me</router-link>
        <!-- 路由出口 -->
        <!-- 路由匹配到的组件将渲染在这里 -->
        <router-view></router-view>
    </div>
</template>

<script>
export default {
    name: "App",
};
</script>

2、路由模式

浏览器地址栏输入http://localhost:8000(webpack开启热更新),页面会自动跳转至http://localhost:8000/#/home,因为我们定义路由时配置了服务器根路径重定向到/home,细心的你还会发现服务器根目录home之间多了一个#号,这是因为vue-router 默认 hash 模式 —— 使用 URL 的 hash 来模拟一个完整的 URL,于是当 URL 改变时,页面不会重新加载。

但是hash看起来就像无意义的字符排列,不太好看也不符合我们一般的网址浏览习惯。

所以,如果不想要很丑的 hash,我们可以使用路由的 history 模式,这种模式充分利用 history.pushState API 来完成 URL 跳转而无须重新加载页面:

const router = new VueRouter({
  mode: 'history',
  routes: [...]
})

当你使用 history 模式时,URL 就像正常的 url,例如 http://yoursite.com/user/id,也好看!

不过这种模式要玩好,还需要后台配置支持。因为我们的应用是个单页客户端应用,如果后台没有正确的配置,当用户在浏览器直接访问 http://oursite.com/user/id 就会返回 404,这就不好看了。

所以呢,你要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面。

但一般我们不会在每次用户输入错误的 URL 都返回同一个index.html页面,不然用户可能还不会意识到自己输入的是一个错误的 URL,我们希望给他一个友好的提示,为此提供一个漂亮的404页面,告诉用户他打开了一个美丽的错误。
vue-router也为我们提供了这样的机制:

{
   path:'*',
   component:Error
}

这里的path: '*'就是找不到页面时的配置,component组件一个Error.vue的文件。

3、路由跳转和传参

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

注意: 在 Vue 实例内部,可以通过 $router 访问路由实例,通过 this.$route 访问当前路由。
this.$router 相当于一个全局的路由器对象,包含了很多属性和对象(比如 history 对象),任何页面都可以调用其 push(), replace(), go() 等方法。
this.$route 表示当前路由对象,每一个路由都会有一个 route 对象,是一个局部的对象,可以获取对应的 name, path, params, query 等属性。

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

  • 路由跳转
// 声名式
<router-link to="/home">Home</router-link> // 路径字符串
<router-link :to="{path: '/home'}">Home</router-link> // 对象
<router-link :to="{name: 'home'}">Home</router-link> // 命名的路由

// 编程式
this.$router.push('home') // 字符串
this.$router.push({ path: '/home' }) // 对象
this.$router.push({ name: 'home'}) // 命名的路由

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

  • 路由传参
    传参有两种方式,分别是 paramsquery,比如我想在URL中传入用户ID:
params请求方式: '/home/1' //访问时,可以通过 this.$route.params.userId 获取到参数值
query请求方式: '/home/?userId=1' //访问时,可以通过 this.$route.query.userId 获取到参数值

其中使用 `params` 方式需要事先在路由定义中配置相应的传参形式, `query` 则不需要:
const routes = [
    {
        path: '/home/:userId', // 如果要传多个参数,比如还要增加一个articleId,直接以相同的形式拼接即可/home/:userId/:articleId
        name: 'home',
        component: Home,
    }
}

可以说 params 相当于 POST 请求,参数不会在地址栏中显示。而query 相当于 GET 请求,页面跳转的时候,可以在地址栏看到请求的参数。

接下对比一下两种传参方式:

// query
this.$router.push('home/?userId=1') // 字符串
this.$router.push({ path: '/home', query: {userId: 1} }) // 对象
this.$router.push({ name: 'home', query: {userId: 1} }) // 命名的路由

// params
this.$router.push('home/1') // 字符串
this.$router.push({ path: '/home', params: {userId: 1} }) // 这里的 params 不生效
// 注意:如果使用了 path,params 会被忽略,因为 params 只能用 name 来引入路由,而不同于上述例子中的 query,此时你需要提供路由的 name 或手写完整的带有参数的 path:
this.$router.push({ path: '/home/1' }) // { path: `/home/${userId}` }
this.$router.push({ name: 'home', params: {userId: 1} }) // 命名的路由

以上规则也适用于 router-link 组件的 to 属性。

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

router.push 很像,唯一的不同就是,router.push会向 history 栈添加一个新的记录,所以当用户点击浏览器后退按钮时,则回到之前的 URL。而 router.replace 不会向 history 添加新记录,而是跟它的方法名一样 —— 替换掉当前的 history 记录。

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.pushrouter.replacerouter.gowindow.history.pushStatewindow.history.replaceStatewindow.history.go 好像, 实际上它们确实是效仿 window.history API 的。

经过以上讲解,我们知道可以在组件内部可通过 this.$route 获取当前路由对象的属性,但这样意味着我们的组件必须跟 Vue-router 偶合在一起,从而只能在某些特定的 URL 上使用,限制了其灵活性。
那么有没有更好的办法,使我们的组件既可以使用路由又可以作为单纯的组件使用呢?答案是肯定的,请看下一节。

4、路由props传参解耦

在定义路由时,我们需要在对应的配置项中添加一个 props 属性,设置为 ture 时,我们在对应组件中可以通过 props: ['userId'] 获取路由中的参数(userId参数)值。

const routes = [
    {
        path: '/home/:userId',
        name: 'home',
        component: Home,
        props: ture
    }
}

以上 props 属性值是布尔值,其实它还可以是一个对象或者回调函数。
这里讲解下回调函数,它传入的参数是当前 route 路由对象,即第3节中的this.$route,然后我们就可以灵活的获取到所需要的参数了:

// query: http://localhost:8000/home?userId=1&articleId=2
const routes = [
    {
        path: '/home',
        name: 'home',
        component: Home,
        props: (route) => ({
            userId: route.query.userId,
            articleId: route.query.articleId
        })
    }
}

// params: http://localhost:8000/home/1/2
const routes = [
    {
        path: '/home/:userId/:articleId',
        name: 'home',
        component: Home,
        props: (route) => ({
            userId: route.params.userId,
            articleId: route.params.articleId
        })
    }
}

这样写的好处是,可以使你的组件可复用性更高,它依赖的参数值,我们完全可以通过 props 传进去,而不需要依赖路由对象来获取,这就是一个解耦的过程,所以比较推荐通过声明 props 这种方式。

5、嵌套路由

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

/home/user                    		  /home/article
+------------------+                  +-----------------+
| Home             |                  | Home            |
| +--------------+ |                  | +-------------+ |
| | User         | |  +------------>  | | Article     | |
| |              | |                  | |             | |
| +--------------+ |                  | +-------------+ |
+------------------+                  +-----------------+

借助 vue-router,使用嵌套路由配置,就可以很简单地表达这种关系。
要在嵌套的出口中渲染组件,需要在路由定义中使用 children 配置:

const routes = [
    {
        path: '/home',
        name: 'home',
        component: Home,
        children: [
        	{
        		// 当 /home/user 匹配成功,
	            // User组件 会被渲染在 Home组件 的 <router-view> 中
	            path: 'user',
	            component: User
        	},
        	{
        		// 当 /home/article 匹配成功,
	            // Article组件 会被渲染在 Home组件 的 <router-view> 中
	            path: 'article',
	            component: Article
        	}
        ]
    }
}

因为 UserArticle 都是 Home 的嵌套子路由,所以需要在 Home 组件模板中添加这两个子路由的 <router-view> 路由出口,这样当 URL 访问 /home/user/home/article 时,对应子路由组件中的内容才会被渲染到上一级路由的 <router-view> 中,我们可以认为 <router-view> 就是一个占位符。

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

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

基于上面的配置,当你访问一个不存在的子路由,如 /home/foo ,此时 Home 组件中的 <router-view> 是不会渲染任何东西的,这是因为没有匹配到合适的子路由。如果你想要渲染点什么,可以提供一个 空的 子路由:

const routes = [
    {
        path: '/home',
        name: 'home',
        component: Home,
        children: [
        	{
        		// 当 /home 匹配成功,
	            // Other组件 会被渲染在 Home组件 的 <router-view> 中
	            path: '',
	            component: Other
        	},
        	// ...其他子路由
        ]
    }
}

6、路由钩子

在某些情况下,当路由跳转前或跳转后、进入、离开某一个路由前、后,需要做某些操作,就可以使用路由钩子来监听路由的变化

全局路由钩子

// 前置守卫
router.beforeEach((to, from, next) => {
    //会在任意路由跳转前执行,next一定要记着执行,不然路由不能跳转了
  console.log('router beforeEach')
  if (to.fullPath === '/app') {
    // next('/login')//不仅可以写字符串还可以是一个对象
    next({path: '/login', replace})
  } else {
    next()
  }
})

// 解析守卫
router.beforeResolve((to, from, next) => {
  // 和全局前置守卫 router.beforeEach 类似,区别是在导航被确认之前, 同时在所有组件内守卫和异步路由组件被解析之后 ,解析守卫才被调用
  console.log("router beforeResolve")
  next();
});

// 后置钩子
router.afterEach((to, from) => {
    // 会在任意路由跳转后执行,没有 next 函数也不会改变导航本身
    // 可以利用全局后置钩子在导航结束后执行一些需要的操作
  console.log('afterEach')
})

路由独享守卫
它是该路由独享的,只有进入该路由时才会执行这个守卫,其参数与全局前置守卫的方法参数是一样的。

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

路由组件钩子

beforeRouteEnter (to, from, next) {
    // 在渲染该组件的对应路由被 confirm 前调用
    // 不能获取组件实例 this
    // 因为当守卫执行前,组件实例还没被创建
    next(vm => {
		// 通过回调方式获取到该组件的实例,这里的 vm 相当于 this
	})
},
beforeRouteUpdate (to, from, next) {
    // 在当前路由改变,但是该组件被复用时调用
    // 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
    // 由于会渲染同样的 Foo 组件,因此组件实例会被复用,而这个钩子就会在这个情况下被调用。
    // 可以访问组件实例 this

	// 可用于解决了不刷新页面,改变路由参数时,mounted或者created生命周期不触发,获取不到参数变化问题(组件复用,生命周期只触发一次)
},
beforeRouteLeave (to, from, next) {
    // 导航离开该组件的对应路由时调用
    // 可以访问组件实例 this
	
	// 一般用于离开页面之前,用户表单未提交或者未完成一些操作时,给用户的一些提示信息
}

完整的导航解析流程

7、路由元信息

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

const router = new VueRouter({
  routes: [
    {
      path: '/foo',
      component: Foo,
      children: [
        {
          path: 'bar',
          component: Bar,
          // a meta field
          meta: { requiresAuth: true }
        }
      ]
    }
  ]
})
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章