# Vue Router 3.x > Vue Router 是 [Vue.js](http://cn.vuejs.org/) 官方的路由管理器。它和 Vue.js 的核心深度集成,让构建单页面应用变得易如反掌。包含的功能有: > > * 嵌套的路由/视图表 > * 模块化的、基于组件的路由配置 > * 路由参数、查询、通配符 > * 基于 Vue.js 过渡系统的视图过渡效果 > * 细粒度的导航控制 > * 带有自动激活的 CSS class 的链接 > * HTML5 历史模式或 hash 模式,在 IE9 中自动降级 > * 自定义的滚动条行为 Vue Router官网: https://v3.router.vuejs.org/ ## 使用 ### 安装 ```sh npm install vue-router ``` ### 使用 1. 导入 `vue vue-router` 2. `Vue.use(Router)` 安装路由插件 3. 创建路由映射表 routes 4. `new Router()` 创建路由实例 5. `App.vue` 添加 `` 根据url 渲染对应的组件 6. 在 `new Vue()` 中注入路由实例 ### 名词 * router 路由实例/路由器 * routes 路由映射表/路由配置 * route 当前路由 ### example `src/router/index.js` ```js import Vue from 'vue' import Router from 'vue-router' import approvalsRouter from './modules/approvals' import departmentsRouter from './modules/departments' import employeesRouter from './modules/employees' import permissionRouter from './modules/permission' import attendancesRouter from './modules/attendances' import salarysRouter from './modules/salarys' import settingRouter from './modules/setting' import socialRouter from './modules/social' Vue.use(Router) /* Layout */ import Layout from '@/layout' // 创建静态路由对象: 登录, 首页, 404 export const constantRoutes = [ { path: '/login', component: () => import('@/views/login/index'), hidden: true }, { path: '/', component: Layout, redirect: '/dashboard', children: [{ path: 'dashboard', name: 'Dashboard', component: () => import('@/views/dashboard/index'), meta: { title: '首页', icon: 'dashboard' } }] }, { path: '/404', component: () => import('@/views/404'), hidden: true }, { path: '/import', component: Layout, hidden: true, // 隐藏在左侧菜单中 children: [{ path: '', // 二级路由path什么都不写 表示二级默认路由 component: () => import('@/views/import') }] }, // 404 通配路由应该放到最后, 有顺序 !!! { path: '*', redirect: '/404', hidden: true } ] // 动态路由 export const asyncRoutes = [ departmentsRouter, employeesRouter, settingRouter, permissionRouter, socialRouter, attendancesRouter, salarysRouter, approvalsRouter ] const createRouter = () => new Router({ // mode: 'history', // 需要服务器支持, 默认使用hash模式 scrollBehavior: () => ({ y: 0 }), // 切换路由时, 让页面回到顶部 routes: [...constantRoutes, ...asyncRoutes] // 合并静态路由和动态路由 }) const router = createRouter() // Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465 export function resetRouter() { const newRouter = createRouter() router.matcher = newRouter.matcher // reset router } export default router ``` `src/router/modules/approvals.js` ```js // 导出属于审批的路由规则 import Layout from '@/layout' // { path: '', component: '' } // 每个子模块 其实 都是外层是layout 组件位于layout的二级路由里面 export default { path: '/approvals', // 路径 name: 'approvals', // 给路由规则加一个name component: Layout, // 组件 // 配置二级路的路由表 children: [{ path: '', // 这里当二级路由的path什么都不写的时候 表示该路由为当前二级路由的默认路由 component: () => import('@/views/approvals'), // 路由元信息: 其实就是存储数据的对象 我们可以在这里放置一些信息 meta: { title: '审批', // meta属性的里面的属性随意定义 这里用title,是因为左侧导航会读取路由里的meta里面的title作为显示菜单名称, icon: 'tree-table' } }] } // 当你的访问地址是 /employees的时候,layout组件会显示,二级路由的默认组件也会显示 ``` `src/App.vue` ```vue ``` `src/main.js` ```js // ... new Vue({ el: '#app', router, // ... }) ``` ## 路由跳转 ### \ * `` 默认渲染成 a 标签 * `` 渲染路由匹配的组件 ```html

Hello App!

Go to Foo Go to Bar

``` #### to 绑定对象 ```js User ``` 对应 ```js router.push({ name: 'user', params: { userId: 123 } }) ``` ### 函数 #### router. push() `` 实际就是通过 `router.push()` 实现的, 会向 history 栈添加一个新的记录, 可以后退 ```js router.push(location, onComplete?, onAbort?) ``` example: ```js // 字符串 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 会被忽略。正确写法是使用完整的path, 或者使用name // 的 to 参数也一样 router.push({ path: `/user/${userId}` }) router.push({ name: 'user', params: { userId }}) ``` * query 会在地址栏显示, params 不显示 * 一般 query 用 path, params 用 name #### router.replace() 和 `router. push()` 的区别就是不会往 history 栈添加新记录, 直接替换掉了 ```js router.replace(location, onComplete?, onAbort?) ``` 模板中使用添加 `replace` 属性 ```html ``` #### router. go() 在 history 记录中向前或者后退多少步,类似 `window.history.go(n)` ```js // 在浏览器记录中前进一步,等同于 history.forward() router.go(1) // 后退一步记录,等同于 history.back() router.go(-1) // 前进 3 步记录 router.go(3) // 如果 history 记录不够用,静默失败 router.go(-100) router.go(100) ``` ## 动态路由 ### 动态路径 ```json { path: '/user/:id', component: User } ``` * 也可以有多个参数 ```json { path: '/user/:user_id/post/post_id', component: Post } ``` 可以通过 `$route.params` 拿到路径信息 ( `$route.query` 可以拿到查询信息) | 模式 | 匹配路径 | $route.params | | ----------------------------- | ------------------- | -------------------------------------- | | /user/:username | /user/evan | `{ username: 'evan' }` | | /user/:username/post/:post_id | /user/evan/post/123 | `{ username: 'evan', post_id: '123' }` | ### 组件的复用 **路由参数发生变化时, 组件并不会销毁并重新创建, 而是复用, 生命周期勾子只调用一次.** 解决: * 方法一: watch `$route` 的变化 ```js watch: { $route(to, from) { // 对路由变化作出响应... } ``` * 方法二: 使用导航守卫 `beforeRouteUpdate` ```js beforeRouteUpdate(to, from, next) { // react to route changes... // don't forget to call next() } ``` ## 嵌套路由 > 组件嵌套, 路由也对应着嵌套 ``` /user/xiaomin/profile /user/xiaomin/posts +------------------+ +-----------------+ | User | | User | | +--------------+ | | +-------------+ | | | Profile | | +------------> | | Posts | | | | | | | | | | | +--------------+ | | +-------------+ | +------------------+ +-----------------+ ``` 在 `App.vue` 里的 `` 对应 url `/user/:id` 渲染 `User.vue` 页面, 在 `User.vue` 中也有一个 `` 对应 url `/user/:id/profile` 渲染 `Profile.vue` 页面 子路由的配置放在 children 里 ```js const router = new VueRouter({ routes: [ { path: '/user/:id', component: User, children: [ { // 当 /user/:id/profile 匹配成功, // UserProfile 会被渲染在 User 的 中 path: 'profile', component: UserProfile }, { // 当 /user/:id/posts 匹配成功 // UserPosts 会被渲染在 User 的 中 path: 'posts', component: UserPosts } ] } ] }) ``` ### 默认子路由 路径为空时, 在访问上级路径会默认渲染 children 组件 ```js children: [ // path为空, 在访问 /user/foo 时 UserHome 会被渲染在 User 的 中 { path: '', component: UserHome } ] ``` ## 命名视图 ### 同级展示多个视图 * components 值是个对象 * 没有添加 name 的对应 default ```html ``` ```json routes: [ { path: '/', components: { default: Foo, a: Bar, b: Baz } } ] ``` ### 在嵌套路由中同级展示多个视图 ``` /settings/emails /settings/profile +-----------------------------------+ +------------------------------+ | UserSettings | | UserSettings | | +-----+-------------------------+ | | +-----+--------------------+ | | | Nav | UserEmailsSubscriptions | | +------------> | | Nav | UserProfile | | | | +-------------------------+ | | | +--------------------+ | | | | | | | | | UserProfilePreview | | | +-----+-------------------------+ | | +-----+--------------------+ | +-----------------------------------+ +------------------------------+ ``` nav 是普通组件, 在 UserSettings 视图下, 需要同时展示 UserProfile 和 UserProfilePreview 视图 ```json { path: '/settings', component: UserSettings, children: [ { path: 'emails', component: UserEmailsSubscriptions }, { path: 'profile', components: { default: UserProfile, helper: UserProfilePreview } } ] } ``` ## 重定向 从 `/a` 重定向到 `/b` ```json // 路径 routes: [ { path: '/a', redirect: '/b' } ] // 重定向的目标也可以是一个命名的路由: routes: [ { path: '/a', redirect: { name: 'foo' }} ] // 动态返回重定向目标 routes: [ { path: '/a', redirect: to => { // 方法接收 目标路由 作为参数 // return 重定向的 字符串路径/路径对象 }} ] ``` ## 别名 访问 `/b` 就和访问 `/a` 一样 ```js routes: [ { path: '/a', component: A, alias: '/b' } ] ``` ## hash 模式和 history 模式 hash 模式 url 中带 `#`, 实际页面不会重新加载 history 需要后端配合配置, 设置 nginx 等相关软件, 把所有 url 都返回 index. html ```conf // nginx location / { try_files $uri $uri/ /index.html; } ``` 1. 阻止默认跳转行为 2. 使用 history.pushState() 更新URL 3. 通过路由映射匹配组件 4. 仅更新\内容区域 ## 路由组件传参 * 在路由映射表添加 `props`, 然后在组件的 `props` 里接收, 这样就不会导致于组件只能在某些 url 中使用 ### 布尔模式 ```json routes: [ // props 被设置为 true,route.params 将会被设置为组件属性 { path: '/user/:id', component: User, props: true }, // 对于包含命名视图的路由,你必须分别为每个命名视图添加 props 选项: { path: '/user/:id', components: { default: User, sidebar: Sidebar }, props: { default: true, sidebar: false } } ] ``` ```vue ``` ### 对象模式 * 这个对象的所有属性会被直接作为props传递给组件, 适用于 props 是静态的 ```json routes: [ { path: '/promotion/from-newsletter', component: Promotion, props: { newsletterPopup: false } } ] ``` ```vue ``` ### 函数模式 结合静态值和路由, 返回需要的属性 ```json routes: [ { path: '/search', component: SearchUser, props: route => ({ query: route.query.q }) } ] ``` ## 导航守卫 > 路由正在发生改变时, 进行 跳转或取消 ### router.beforeEach() 全局前置守卫 ```js router.beforeEach((to, from, next) => { console.log(to) //要进入的路由对象 console.log(from) //正离开的路由对象 console.log(next) //函数 next() //放行, 放最后一行, 不放行的话一直等待中, 不会刷出页面 //next()进行管道中的下一个钩子 //next('/') 或者next({path:'/'}) 跳转到一个不同的地址 //next(false) 中断当前的导航, 重置到 from 路由对应的地址 //next(error) 导航会被终止且该错误会被传递给 router.onError() 注册过的回调 }) ``` ### router.afterEach() 全局后置钩子 * 不会接受 next 函数也不会改变导航本身 ```js router.afterEach((to, from) => { // ... }) ``` ### 路由独享的守卫 ```json routes: [ { path: '/foo', component: Foo, beforeEnter: (to, from, next) => { // ... } } ] ``` ### 组件内的守卫 ```vue ``` ## 路由元信息 > 一个路由匹配到的所有路由记录会暴露为 `$route.matched` 数组 定义路由的时候可以配置 `meta` 字段 ```js { name: 'useredit', path: '/useredit', component: () => import('@/views/UserEdit.vue'), meta: { isAuth: true } } ``` ### 在组件中访问 `to.meta.isAuth` `from.meta.isAuth` ### 在全局导航守卫 ```js router.beforeEach((to, from, next) => { if (to.matched.some(record => record.meta.requiresAuth)) { // 检查认证状态 if (!store.state.isLoggedIn) next('/login') else next() } else { next() } }) ``` ## 路由懒加载 > 当路由被访问的时候才加载对应组件 异步加载组件(懒加载): ```js { // 进行路由配置,规定'/'引入到home组件 path: '/', component: resolve => require(['../pages/home.vue'], resolve), meta: { title: 'home' } }] ``` 更多的写法 ```js { path: '/', component: () => import('../pages/home.vue'), meta: { title: 'home' } } ``` 把导入写到外边 ```js const Home = () => import('../pages/home.vue') // ... { path: '/', component: Home, meta: { title: 'home' } } ``` ### 组件按组分块 把某个路由下的所有组件都打包在同个异步块 (chunk) 中。只需要使用命名 chunk ```js const Foo = () => import(/* webpackChunkName: "group-foo" */ './Foo.vue') const Bar = () => import(/* webpackChunkName: "group-foo" */ './Bar.vue') const Baz = () => import(/* webpackChunkName: "group-foo" */ './Baz.vue') ```