1小时 Vue Router 从入门到放弃

​ 本文所用项目 GitHub 地址:https://github.com/trp1119/vue-router

​ 本文与 Vue Router 官方文档 区别:非官方文档功能罗列,而是从实际应用角度一步步进行 Vue Router 设置及功能介绍。水平有限,难免存有纰漏,欢迎在评论区指正交流。

前言

​ 现在做前端 Web App,路由是必不可缺的功能。以前网站开发,由链接跳转到后端,进行模板渲染,产生一个新的 HTML 返回给浏览器端,等浏览器显示出页面,完成一次路由跳转。而对于现在的单页面应用开发,页面跳转不经过后端服务器,页面渲染内容全部来自于JavaScript。既然路由跳转由前端来做,则需要称职合理的工具处理前端路由。

​ 现在基本上每个前端框架都会配备路由管理工具,像 Vue 使用的 Vue RouterReact 使用的 React Router,还有不和框架耦合的 History 等工具。这些工具,在前端 SAP 开发中起着越来越重要的作用。

​ 什么是 Vue RouterVue RouterVue.js 官方的路由管理器。通过将组件映射到路由 routes,然后使用 Vue Router 进行渲染,实现路由功能。

​ 本文 Vue Router 基于模块化工程使用。在使用之前,需要先安装 vue-router 插件。

npm i vue-router -S
1 Vue Router 之集成

vue-router 插件安装完毕后,需要进行路由的部署。在 src 文件夹下 创建 router 文件夹并创建 router.jsroutes.js 文件。

​ 为什么分开配置?因为在项目变庞大之后,路由配置会非常多,所以可以单独配置一个和路由映射关系有关的文件 routes.js,而 router.js 主要进行路由器 router 的内容配置。

1.1 在 routes.js 中配置路由映射关系

export default 一个数组,数组中的每个对象都是一个路由项。

import Login from '../view/login' // 1.引入组件
import Home from '../view/home'

export default [ // 4.导出路由映射关系
  {
    path: '/login', // 2.设置跳转路由
    component: Login // 3.映射组件
  },
  {
    path: '/home',
    component: Home
  }
]

​ 不推荐这么使用,因为这样每次在全局 import这个 router 的时候,引入的都是同一个 router,如果需要每次引入的时候新创建一个 router,这种方式是做不到的。

​ 另外,由于项目需要服务端渲染。每次 export default 同一个 router,会导致在服务端渲染时出现内存溢出的问题。每次服务端渲染都会重新生成一个新的 app,由于 router 只有一个对象(共用同一个),在服务端渲染流程结束后,app 对象没有释放,每次都会缓存新的 app,导致内存没有下降,一直处于高点,随着内存逐渐累加,出现内存溢出。

1.2 默认路由跳转

​ 在路由配置文件 routes.js 中配置默认路由跳转。

export default [
  {
    path: '/', // 根路径
    redirect: '/login' // 默认跳转路由
  },
  {
    path: '/login',
    component: Login
  },
  {
    path: '/home',
    component: Home
  }
]
1.3 在 route.js 中配置路由器

1.3.1 配置方法1

import Router from 'vue-router' // 1.引入 Vue Router 插件中 Router 路由器
import routes from './routes' // 2.引入路由映射关系

export default new Router({ // 4.创建并导出 Router 对象实例
  routes // 3.传入映射关系配置
})

1.3.2 配置方法2

import Router from 'vue-router' // 1.引入 Vue Router 插件中 Router 路由器
import routes from './routes' // 2.引入路由映射关系

export default () => { // 4.创建并导出 Router 方法(注意这里导出的是方法,并没有在此创建对象实例)
  return new Router({
    routes // 3.传入映射关系配置
  })
}

​ 每次生成新的 Router 对象实例,渲染结束同 app 一起释放,不会出现内存溢出问题。

1.4 在入口文件 main.js 中注入路由功能

​ 引入 VueRouter 插件并使用(Vue.use())以使用路由功能,引入前面配置好的 Router 方法并使用以使用路由设置及映射关系。

import Vue from 'vue'
import VueRouter from 'vue-router' // 引入 VueRouter 路由插件

import App from './App.vue'

import createRouter from './router/router' // 引入带映射关系的 Router 方法

Vue.config.productionTip = false

Vue.use(VueRouter) // 使用 VueRouter 插件
const router = createRouter() // 在此创建 router 对象

new Vue({
  router, // 注入路由。通过在根节点 Vue 实例上挂载 router 对象,使每个组件都能拿到这个 router 对象,从而让整个应用都有路由功能。(VueRouter内部实现此功能)
  render: h => h(App),
}).$mount('#app')
1.5 使用 router-view 渲染匹配到的组件

​ 在 App.js 中渲染页面。


1.6 使用 router-link 进行页面跳转

​ router-link 的实现是 a 标签。点击对应链接后会在 中渲染路由匹配到的组件。

​ 页面中可点击的路由一般都会通过 router-link,因为 router-link 的实现是 a 标签,a 标签中的 href 有利于网站的 SEO,但 a 标签默认的行为是页面跳转,而不是前端路由跳转。即 router-link 不单纯是 a 标签的实现,而是通过内部事件,实现前端路由跳转。


1-6.png
1.7 Vue Router 基础集成设置完毕

​ 设置完毕,在页面打开路由。可以发现 Vue Router 自动在网站域名后添加了 #/ ,这是因为 Vue Router 默认的路由形式是使用哈希路由。作为 SAP 且有服务端渲染的情况下,路由改为 histiory 形式(无 #/)更为合理且更利于 SEO 优化。

1-7.png
2 Vue Router 之配置

​ 创建 router 实例时常用参数配置及作用。

2.1 mode配置

Vue Router 默认使用哈希路由(路由中带 #),但哈希路由一般用来定位,而不是做路由状态的记录。同时,哈希路由不会被搜索引擎解析,影响网站 SEO 。所以在服务端渲染时,一般不使用哈西路由。

​ 使用 history 路由,可在路由器配置文件 route.js 中配置 modemode 有两种参数,一个是默认的 hash ,一个是 history

export default () => {
  return new Router({
    routes, // 路由配置项
    // mode: 'hash', // 默认哈西路由 
    mode:'history',  // 配置 hisyory 路由
  })
}
2-1-1.png
2-1-2.png
2.2 base 配置

​ 在 routes.js 配置的所有路由前天添加 /自定义base/,这个路径作为所有应用的基路径。在应用中不论是用 route-link 还是 route 对象跳转,只要是通过 vue routerapi 跳转,都会加上此基路径。但是把路径中 /base/ 手动去掉,仍然会显示,所有 base 不是强制性的。在区分页面路径和其他类型路径的时候会用到。

​ 在 mode 配置为 hash 时,base 配置不生效。

export default () => {
  return new Router({
    routes,
    mode:'history',
    base: '/mybase/', // 注意加前后/,base 在 mode 配置为 hash 时不生效
  })
}
2.3 linkActiveClass 与 linkExactActiveClass 配置

​ 查看 1.5 中配置的 route-link 浏览器解析源码,会发现激活的路由默认添加 router-link-exact-activerouter-link-active 样式,可以在 linkActiveClasslinkExactActiveClass 中自定义样式名称,然后在 css 中自定义全局样式。

2-3-1.png
export default () => {
  return new Router({
    routes,
    mode:'history',
    base: '/mybase/',
    linkActiveClass: 'active-link', // 配置 route-link 链接 class,路径不完全匹配时添加 class 为 linkActiveClass
    linkExactActiveClass: 'exact-active-link', // 路径完全匹配时添加 class 为 linkExactActiveClass linkActiveClass
  })
}

​ 配置完毕,可以看到 a 标签 class 样式已经变为配置样式。

2-3-2.png

​ 那么 linkActiveClasslinkExactActiveClass 有什么区别?

​ 展示区别前,先在 routes.jsapp.vue 中补充路由。

// routes.js
export default [
  {
    path: '/',
    redirect: '/login'
  },
  {
    path: '/login',
    component: Login
  },
  {
    path: '/home',
    component: Home
  },
  {
    path: '/home/exact', // 补充路由
    component: Home
  }
]


​ 补充完毕,点击“去Home/exact页”后,查看浏览器源码,会发现 “去Home页”路由少了 my-exact-active-link 样式,但仍保留 my-active-link 样式。即路径不完全匹配时添加 class 为 my-active-linklinkActiveClass 中配置的样式),路径完全匹配时添加 class 为 my-exact-active-link my-active-linklinkExactActiveClasslinkActiveClass 中配置的样式)。

2-3-3.png
2.4 scrollBehavior 配置

scrollBehavior 用于配置页面跳转时是否滚动,scrollBehavior 接收一个方法,有三个参数 tofromsavePosition,其中,参数 to 为去的路由,from 跳转到 to 路由之前的路由。如果之前进入过这个路由,savePosition 用于保存之前滚动条滚动的位置。tofrom 不是字符串,而是完整的 router 的对象,包含路由的 paramsquery 等信息。

export default () => {
  return new Router({
    routes,
    mode:'history',
    base: '/mybase/',
    linkActiveClass: 'active-link',
    linkExactActiveClass: 'exact-active-link',
    scrollBehavior (to, from, savePosition) { // 页面跳转时是否需要滚动
      // to 去往路由 from 来时路由 savePosition 记录滚动条位置
      if (savePosition) {
        return savePosition // 如果进入过,回到之前的位置
      } else {
        return { x: 0, y: 0 } // 如果未因如果,滚动到原点
      }
    },
  })
}
2.5 parseQuery 与 stringifyQuery

​ 例如 http://localhost/login?name=zhangsan&password=12345url 后带的参数 queryname=zhangsan&password=12345string 格式,需转为 JSON Object,Vue 是默认做转换的,但如果有默认需求,可通过配置 parseQuery 进行转换。将 Object 转为 stiring 可在 stringifyQuery 中配置。

export default () => {
  return new Router({
    routes,
    mode:'history',
    base: '/mybase/',
    linkActiveClass: 'active-link',
    linkExactActiveClass: 'exact-active-link',
    scrollBehavior (to, from, savePosition) {
      if (savePosition) {
        return savePosition
      } else {
        return { x: 0, y: 0 }
      }
    },
    parseQuery (query) {},
    stringifyQuery (obj) {},
  })
}
2.6 fallback配置

​ 并不是所有浏览器都支持 history 前端路由(页面不跳转但内容切换),在不支持 history 前端路由的浏览器中,Vue 自动 fallback hash 路由模式,如果不希望 Vue 进行此操作,可将 fallback 设置为 false。当 fallbackfalse 时,Vue 不会去自动处理,在不支持 histiory 路由的浏览器中,单页面应用就变为多页面应用,每次页面跳转都会去后端请求数据返回内容,耗时。

export default () => {
  return new Router({
    routes,
    mode:'history',
    base: '/mybase/',
    linkActiveClass: 'active-link',
    linkExactActiveClass: 'exact-active-link',
    scrollBehavior (to, from, savePosition) {
      if (savePosition) {
        return savePosition
      } else {
        return { x: 0, y: 0 }
      }
    },
    parseQuery (query) {},
    stringifyQuery (obj) {},
    fallback: true,
  })
}
3 route 配置及参数传递
3.1 name 配置

​ 使用 name 对路由进行命名,和 pathcomponent 的命名无关联,可以使用这个 name 进行路由跳转。

// routes.js
export default [
  //...
  {
    path: '/login',
    component: Login,
    name: Login, // name 命名
  },
  //...
]

​ 在路由跳转配置中,name 传入的是 JSON Object,希望 Vue 去解析它而不是当做 stirng 处理,所以需要使用 v-bind 进行数据绑定



3.2 meta 配置

meta 用于保存路由信息,

​ 在 html head 标签中会使用 meta 保存页面元信息,这些信息有利于 SEO 优化。在写 Vue 组件的时候,很难写入这些信息,则可在路由配置中加入 meta,这些信息,可以在拿到路由 route 对象时通过 .mate 拿到这些信息并进行设置。

// routes.js
export default [
  //...
  {
    path: '/login',
    component: Login,
    name: Login,
    meta: { // meta 配置
      title: 'This is Home Page',
      description: 'This is Home Page description'
    },
  },
  //...
]
3.3 children 嵌套路由配置

children 也是个数组,是子路由,配置和父级路由配置相同。

// routes.js
export default [
  //...
  {
    path: '/login',
    component: Login,
    name: Login,
    meta: { // meta 配置
      title: 'This is Login Page',
      description: 'This is Login Page description'
    },
    children: [ // 子路由(嵌套路由),路由匹配的内容都是通过 展示的,/login 下的 children子路由,则其  在父级路由组件 Login 中显示。
      {
        path: 'loginChild',
        component: () => import('../view/loginChild')
      }
    ],
  },
  //...
]

​ 在 login.vue 中加入



1小时 Vue Router 从入门到放弃_第1张图片
3-3.png
3.4 transition 过渡动画

​ 使用 transition 包裹 ,并进行动画样式设置。如果包裹根页面(App.vue)中的 ,会为每个路由切换都添加过渡动画。想要某个页面使用切换效果,使用 transition 包裹单个组件的 即可。







3.5 :id 动态传参

path: '/login/:id',动态绑定 id,当页面路径为 /login/123 时,则 id123,在页面中通过 this.$route.params 可以拿到键值对。

// routes.js
export default [
  //...
  {
    path: '/login/:id',
    component: Login,
    name: Login,
    meta: {
      title: 'This is Login Page',
      description: 'This is Login Page description'
    },
    children: [
      {
        path: 'loginChild',
        component: () => import('../view/loginChild')
      }
    ],
  },
  //...
]




​ 页面路径 http://localhost:8080/mybase/login/123?name=zhangsan&password=123456

this.$route 对象打印结果:

1小时 Vue Router 从入门到放弃_第2张图片
3-5.png

​ 可以看出,路由 path 中不会出现 router.js 中设置的 base,参数以 json 对象的形式存放在 query 中。

3.6 props

​ 定义 propstrue 后,可以将 :id 作为 props 传入组件 Login 中,即在组件中,用 props 直接接收即可, props: ['id'],代替父组件传值。这种方式可以实现在组件内不需要写 this.$route 而用 props 直接接收。

​ 如果组件中写了 this.$route 这种写法,路由与组件耦合在一起,组件就不能单独拿去复用(因为组件绑定了路由),可能会存在路由不匹配。如果用 props 声明,在其他地方也可以使用这个组件,因为 依赖的 id 值可以改为通过父组件传入,不再依赖路由读取,实现解耦。

// routes.js
export default [
  //...
  {
    path: '/login/:id',
    props: true,
    component: Login,
    name: Login,
    meta: {
      title: 'This is Login Page',
      description: 'This is Login Page description'
    },
    children: [
      {
        path: 'loginChild',
        component: () => import('../view/loginChild')
      }
    ],
  },
  //...
]




​ 路由中 props 配置除了从 path 中取参数,也可以自己定义内容,这时,页面 props 中接收的内容即为 路由 props 中设置的值。

// routes.js
export default [
  //...
  {
    path: '/login/:id',
    props: {
      id: '456',
      sex: 'man'
    },
    component: Login,
    name: Login,
    meta: {
      title: 'This is Login Page',
      description: 'This is Login Page description'
    },
    children: [
      {
        path: 'loginChild',
        component: () => import('../view/loginChild')
      }
    ],
  },
  //...
]




​ 路由 props 也可以从路由中取值,例如取路由 query 中的值,这样,页面就无需采用 this.$route.query.xxx 方式去取值。

// routes.js
export default [
  //...
  {
    path: '/login/:id',
    props: (route) => ({name:route,query.name}), // 这里的 route 相当于组件中的 this.$route
    component: Login,
    name: Login,
    meta: {
      title: 'This is Login Page',
      description: 'This is Login Page description'
    },
    children: [
      {
        path: 'loginChild',
        component: () => import('../view/loginChild')
      }
    ],
  },
  //...
]

​ 推荐不要拘泥于 this.$route 写法,尽量实现组件与路由解耦,以提高组件复用性。

4 高级功能
4.1 命名视图

​ 前面写的一个页面中只有一个路由出口 ,但如果同一个页面中有两部分,在不同的路由下显示不同的内容,这时可在同一个组件内部使用两个 ,并对其进行命名,对于不同的 ,在不同的名字下赋予不同的组件。

​ 在路由项配置文件 routes.js 中,使用 components 分配不同的组件。

// routes.js
export default [
  //...
  {
    path: '/login/:id',
    props: {
      id: '456',
      sex: 'man'
    },
    components: {
        default: Login, // 未命名,默认使用 default
        myRouteView: Home // 不可采用 my-route-view 形式
    },
    name: Login,
    meta: {
      title: 'This is Login Page',
      description: 'This is Login Page description'
    },
    children: [
      {
        path: 'loginChild',
        component: () => import('../view/loginChild')
      }
    ],
  },
  //...
]


1小时 Vue Router 从入门到放弃_第3张图片
4-1.png
4.2 导航守卫(导航钩子)

4.2.1 全局导航守卫

main.js 中进行全局导航守卫注册。可以用 beforeEach 验证用户是否登录,如果未登录,自动跳转至登录页面。next() 中参数配置同 route-link 中参数配置。

必须执行 next(),不然不会进入钩子。

// main.js
import Vue from 'vue'
import VueRouter from 'vue-router'

import App from './App.vue'

import createRouter from './router/router'

Vue.config.productionTip = false

Vue.use(VueRouter)
const router = createRouter()

// 全局导航守卫
router.beforeEach((to, from, next) => {
  console.log('main beforeEach invoked')
  if (to.fullPath !== '/login') { // 路由匹配到才回去执行 next,如果不匹配,只打印 beforeEach invoked,而不会继续打印beforeResolve invoked 和 afterEach invoked
    next('/login')
  }else {
    next () // 执行 next 后路由才会被跳转
  }
})
router.beforeResolve((to, from, next) => {
  console.log('main beforeResolve invoked')
  next()
})
router.afterEach((to, from) => { // 每次导航跳转结束后被触发,因为已经跳转,所以不再需要 next
  console.log('main afterEach invoked')
})

new Vue({
  router,
  render: h => h(App),
}).$mount('#app')

页面触发顺序(每次路由变化都会被触发):

4-2-1-1.png

4.2.2 路由导航守卫

// routes.js
export default [
  //...
  {
    path: '/login/:id',
    props: {
      id: '456',
      sex: 'man'
    },
    components: {
        default: Login,
        myRouteView: Home
    },
    name: Login,
    meta: {
      title: 'This is Login Page',
      description: 'This is Login Page description'
    },
    children: [
      {
        path: 'loginChild',
        component: () => import('../view/loginChild')
      }
    ],
    // 在路由配置时增加钩子
    // 进入这个路由前调用这个钩子,调用顺序在 beforeEach 和 beforeResolve 之间
    beforeEnter (to, from, next) {
      console.log('route beforeEnter')
      next()
    }
  },
  //...
]

页面触发顺序:

4-2-2-1.png

4.2.3 组件导航守卫

beforeRouteEnter 时,组件尚未被创建(未实例化),故还无法取到 this,无法调用 this 上任何内容,也无法给 this 赋值。这时可使用组件对象 vm (即组件创建后 this 的对象)。

beforeRouteLeave 可用于让用户确认是否离开次路由。





进入页面时触发顺序(beforeRouteEnter):

4-2-3-1.png

更新页面时触发顺序(beforeRouteUpdate,例如 login/123 切换至 login/456Login 组件不会被销毁后重建,而是内容更新):

4-2-3-2.png

切换页面时触发顺序(beforeRouteLeave):

4-2-3-3.png

提示:例如 login/123 切换至 login/456 是,由于 Login 组件复用,不会再次去调用 mounted,此时可使用 beforeRouteUpdate

4.3 异步组件

路由在非常多的情况下,如果通过 webpack 一次性打包所有代码,会导致 js 文件庞大,初次加载耗时,且访问某一页面时也加载了其他页面的 js 代码,造成资源浪费。如果对于某一路由,只加载对应页面代码及核心代码,其他页面代码待访问时再加载,可使用异步组件加载,提高首屏加载速度。

// routes.js
export default [
  //...
  {
    path: '/login/:id',
    props: {
      id: '456',
      sex: 'man'
    },
    components: {
        default: Login,
        myRouteView: Home
    },
    name: Login,
    meta: {
      title: 'This is Login Page',
      description: 'This is Login Page description'
    },
    children: [
      {
        path: 'loginChild',
        component: () => import('../view/loginChild') // 异步组件加载,routers.js 顶部不必再引入 loginChild 组件
      }
    ],
    beforeEnter (to, from, next) {
      console.log('route beforeEnter')
      next()
    }
  },
  //...
]

你可能感兴趣的:(1小时 Vue Router 从入门到放弃)