vue-router
安装:
vue add router
配置
// router.js import Vue from 'vue' import VueRouter from 'vue-router' import Home from '../views/Home.vue' Vue.use(VueRouter) // 引入Router插件 const routes = [ { path: '/', name: 'home', component: () => import('../components/form') }, { path: '/test', name: 'test', // 懒加载 component: () => import('../components/formTest') } ] const router = new VueRouter({ mode: 'history', // 模式:hash | history | abstract base: process.env.BASE_URL, // http://localhost:8080/cart routes }) export default router
指定路由器
// main.js new Vue({ router, render: h => h(App) }).$mount('#app')
路由视图
<router-view/>
导航链接
<router-link to="/">Home</router-link> <router-link to="/about">About</router-link>
路由嵌套
应用界面通常由多层嵌套的组件组合而成。同样的,URL中各段动态路径也按某种结构对应嵌套的各层组件。
配置嵌套路由,router.js
{ path: "/", component: Home, children: [{ path: "/list", name: "list", component: List }] }
动态路由
我们经常需要把某种模式匹配到的所有路由,全部映射到同一个组件
详情页路由配置,router.js
{ path: '/', name: 'home', component: () => import('../components/form'), children: [ { path: '/detail/:id', component: Detail } ] }
获取动态路由的参数
<template> <div> <h2>商品详情</h2> <p>{{$route.params.id}}</p> </div> </template>
另一种传参方式:传递路由组件参数 props
{ path: "detail/:id", component: Detail, props: true }
组件中以属性方式获取:
export default { props: ['id'] }
路由守卫
路由导航过程中有若干生命周期钩子,可以在这里实现逻辑控制
全局守卫,router.js
// 路由配置 { path: "/about", name: "about", meta: { auth: true }, // 需要认证 component: () => import('../components/About.vue') } // 全局守卫 router.beforeEach((to, from, next) => { // 要访问 /about 且未登录需要去登录 if(to.meta.auth && !window.isLogin){ if(window.confirm("请登录")){ window.isLogin = true next() // 登录成功,继续 } else { next('/') // 放弃登录,回首页 } } else { next() // 不需登录,继续 } })
路由独享守卫
{ path: '/test', name: 'test', component: () => import('../components/formTest'), beforeEnter: (to, from, next) => { // 路由内部知道自己需要认证 if(!window.isLogin){ // ... }else{ next() } } }
组件内的守卫
export default { beforeRouteEnter(to, from, next) { }, beforeRouteUpdate(to, from, next) { }, beforeRouteLeave(to, from, next) { } }
vue-router 拓展
动态路由
利用 $router.addRoutes() 可以实现动态路由添加,常用于用户权限控制。
// router.js --伪码 // 返回数据可能是这样的 // [{ // path: '/', // name: 'home', // component: 'Home' // }] // 异步获取路由 api.getRoutes().then(routes => { const routeConfig = routes.map(route => mapComponent(route)) router.addRoutes(routeConfig) }) // 映射关系 const compMap = { 'Home': () => import("./view/Home.vue") } // 递归替换 function mapComponent(route) { route.component = compMap[route.component] if(route.children) { route.children = route.children.map(child => mapComponent(child)) } return route }
面包屑
利用 $route.matched 可得到路由匹配数组,按顺序解析可得路由层次关系
// Breadcrumb.vue watch: { $route: { handler(route) { // [{name:'home',path:'/'},{name:'list',path:'/list'}] console.log(this.$route.matched); // ['home','list'] this.crumbData = this.$route.matched.map(m => m.name || m.redirect); }, immediate: true // 这⼀一⾏行行要加上,让它⼀一开始执⾏行行⼀一次 } }
vue-router 源码实现
通常用法
// krouter.js import Home from "./views/Home"; import About from "./views/About"; import VueRouter from "./kvue-router"; Vue.use(VueRouter); export default new VueRouter({ routes: [ { path: "/", component: Home }, { path: "/about", component: About } ] });
// main.js import router from './krouter'
分析一下需要完成的任务:
1、要能解析 routes 配置,变成一个key为path,value为component的map
2、要能监控url变化事件,把最新的hash值保存到current路由
3、要定义两个全局组件:router-view用于显示匹配组件的内容,router-link用于修改hash
4、current应该是响应式的,这样可以触发 router-view 的重写渲染
具体实现:
创建 krouter.js — 测试代码,相当于 平常使用的 router.js
// krouter.js import Vue from 'vue' import Home from "./views/Home"; import About from "./views/About"; import VueRouter from "./kvue-router"; Vue.use(VueRouter); export default new VueRouter({ routes: [ { path: "/", component: Home }, { path: "/about", component: About } ] }); // main.js import router from './krouter'
kvue-router.js,相当于 import VueRouter from ‘vue-router’ 中的 vue-router插件
let Vue class VueRouter { constructor(options) { this.$options = options // 创建一个路由path和route映射 this.routeMap = {} // 将来当前路径current需要响应式 // 利用Vue响应式原理可以做到这一点 this.app = new Vue({ data: { current: '/' } }) } init() { // 绑定浏览器事件 this.bindEvents() // 解析路由配置 this.createRouteMap(this.$options) // 创建router-link和router-view this.initComponent() } bindEvents() { // 修正this执行,因为我们想在这里拿 router的实例 window.addEventListener('hashchange', this.onHashChange.bind(this)) window.addEventListener('load', this.onHashChange.bind(this)) } onHashChange(){ // localhost/#/hash slice(1)可以拿出#后面的部分 this.app.current = window.location.hash.slice(1) || '/' } createRouteMap(options){ options.routes.forEach(item => { // ['/home']: {path: '/home', component: Home} this.routeMap[item.path] = item }) } initComponent(){ // 声明两个全局组件 Vue.component('router-link', { props: { to: String }, render(h) { // 目标是:<a :href="to">xxx</a> return h('a', {attrs: {href: '#' + this.to}}, this.$slots.default) // jsx的写法 // return <a href={this.to}>{this.$slots.default}</a> } }) Vue.component('router-view', { // 箭头函数能保留this指向,这里指向VueRouter render: h => { const Comp = this.routeMap[this.app.current].component return h(Comp) } }) } } // 把VueRouter变为插件,Vue插件只需要实现一个install方法就行了 VueRouter.install = function(_Vue) { Vue = _Vue // 这里保存,上面使用 // 混入任务 Vue.mixin({ // 将来这个Vue被new的时候,会执行这里的代码 beforeCreate() { // 这里的代码将来会在外面初始化的时候被调用 // 这样外面就实现了Vue扩展 // 这里this就是Vue组件实例 // 但是这里只希望根组件执行一次 if(this.$options.router){ Vue.prototype.$router = this.$options.router // 对路由器进行初始化 this.$options.router.init() } } }) } export default VueRouter
测试注意:
不要出现 view-router 嵌套,因为这里的代码没做嵌套处理