欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

vue全家桶(vue-router简版源码的实现)

程序员文章站 2022-03-22 10:25:27
vue-routerVue Router 是 Vue.js 官⽅的路由管理器。它和 Vue.js 的核⼼深度集成,让构建单页面应用变得易如反掌。嵌套的路由/视图表模块化的、基于组件的路由配置路由参数、查询、通配符基于 Vue.js 过渡系统的视图过渡效果细粒度的导航控制带有自动激活的 CSS class 的链接HTML5 历史模式或 hash 模式,在 IE9 中自动降级自定义的滚动条行为安装: vue add router核⼼步骤:步骤⼀:使⽤vue-router插件,rout...

vue-router

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

  • 嵌套的路由/视图表
  • 模块化的、基于组件的路由配置
  • 路由参数、查询、通配符
  • 基于 Vue.js 过渡系统的视图过渡效果
  • 细粒度的导航控制
  • 带有自动激活的 CSS class 的链接
  • HTML5 历史模式或 hash 模式,在 IE9 中自动降级
  • 自定义的滚动条行为

安装: vue add router

核⼼步骤:

步骤⼀:使⽤vue-router插件,router.js

import Router from 'vue-router'
Vue.use(Router)

步骤二: 声明一个路由表(是一个映射表,path和组件是一个映射关系)

const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home
  },
  {
    path: '/about',
    name: 'About',
    component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
  }
]

步骤三:创建Router实例,router.js(然后导出)

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

步骤四: 在根组件上添加该实例,main.js

import router from './router'
new Vue({
    router,
}).$mount("#app")

步骤五: 添加路由视图,App.vue

<router-view></router-view>

导航链接

<router-link to="/">Home</router-link> 
<router-link to="/about">About</router-link>

以上大概是vue-router的基本使用,下面进入简版源码实现部分

vue-router源码实现

需求分析

  • spa 页面不能刷新
    • hash方式 例如: #/home
    • 或者History api 例如: /about
  • 根据url显示对应的内容
    • router-view
    • 数据响应式: current变量持有url地址,一旦变化,动态重新执行render

任务

  • 实现一个插件(创建VueRouter类和install⽅法)
    • 实现VueRouter类
      • 处理路由选项
      • 监控url的变化
      • 相应变化
    • 实现一个install 方法
      • $router的注册
      • 两个全局组件

创建kvue-router.js

let Vue; // 引⽤构造函数,VueRouter中要使⽤
// 保存选项
class VueRouter {
   constructor(options) {
     this.$options = options;
   }
}
// 插件:实现install⽅法,注册$router
VueRouter.install = function(_Vue) {
  // 引⽤构造函数,VueRouter中要使⽤
  Vue = _Vue;
  // 任务1:挂载$router
  Vue.mixin({
	beforeCreate() {
	  // 只有根组件拥有router选项
	  if (this.$options.router) {
	  // vm.$router
	    Vue.prototype.$router = this.$options.router;
	  }
	}
  });
  // 任务2:实现两个全局组件router-link和router-view
  Vue.component('router-link', Link)
  Vue.component('router-view', View)
};
export default VueRouter;

为什么要⽤混⼊⽅式写?主要原因是use代码在前,Router实例创建在后,⽽install逻辑⼜需要⽤ 到该实例,这里是一个比较巧妙的方法(主要是为了延迟执行)

创建router-view和router-link

创建krouter-link.js

 export default {
    // 定义传入的参数
    props: {
      to: {
        type: String,
        require: true
      }
    },
    render(h) {
      // <router-link to="/home"/>
      // <a href="#/home">XXX</a>
      // 通用性更好
      return h('a',{
        attrs: {
          href: '#' + this.to
        }
      }, this.$slots.default)
      // 需要当前环境支持jsx
      // return <a href={'#' + this.to}> {this.$slots.default}</a>
    }
  }

创建krouter-view.js

export default {
  render(h) {
  // 暂时先不渲染任何内容
    return h(null);
  }
}

监控url变化
定义响应式的current属性,监听hashchange事件

class VueRouter {
  constructor(options) {
  // current应该是响应式的
  Vue.util.defineReactive(this, 'current', '/')
  // 定义响应式的属性current
  const initial = window.location.hash.slice(1) || '/'
  Vue.util.defineReactive(this, 'current', initial)
    // 监听hashchange事件
    window.addEventListener('hashchange', this.onHashChange.bind(this))
    window.addEventListener('load', this.onHashChange.bind(this))
  }
  onHashChange() { 
    this.current = window.location.hash.slice(1)
 }
}

动态获取对应组件,krouter-view.js

export default {
  render(h) {
  // 动态获取对应组件
    let component = null;
    this.$router.$options.routes.forEach(route => {
    if (route.path === this.$router.current) {
      component = route.component
    } 
   });
   return h(component);
  }
}

提前处理路由表
提前处理路由表避免每次都循环

class VueRouter {
  constructor(options) {
	// 缓存path和route映射关系
	this.routeMap = {}
	this.$options.routes.forEach(route => {
	  this.routeMap[route.path] = route
	});
  }
}

使⽤,krouter-view.js

export default {
  render(h) {
    const {routeMap, current} = this.$router
    const component = routeMap[current] ? routeMap[current].component : null;
    return h(component);
  }
}

下面附上完整代码

// 实现一个插件
// 1. 返回一个函数
// 2. 或者返回一个对象,它有一个install方法


let _Vue = null
/**
 * @class VueRouter
 * @param options 选项
 */

class VueRouter {
  constructor(options) {
    // options 配置选项: router - 路由表
    this.$options = options

    // 缓存path 和route的映射关系
    this.routeMap = {}

     // 找到当前url对于的组件
    this.$options.routes.forEach(route => {
      this.routeMap[route.path] = route
    })

    // 需要定义一个响应式的current属性
    const initial = window.location.hash.slice(1) || '/'
    // defineReactive 给一个对象定义响应式数据
    _Vue.util.defineReactive(this, 'current', initial)

    // 监控url的变化
    window.addEventListener('hashchange', this.onHashChange.bind(this))

  }
  onHashChange () {
    this.current = window.location.hash.slice(1)
    console.log(this.current)
  }
}

VueRouter.install = function(Vue) {
  // 引用Vue构造函数,在上面的VueRouter中使用
  _Vue= Vue
  // 1. 挂载$router
  // 利用混入,延迟执行
  Vue.mixin({
    // 此处的this 指的是vue根实例
    beforeCreate() {
      if(this.$options.router) {
        Vue.prototype.$router = this.$options.router
      }
    }
  })

  // 2. 定义两个全局组件router-link, router-view
  Vue.component('router-link', {
    // 定义传入的参数
    props: {
      to: {
        type: String,
        require: true
      }
    },
    render(h) {
      // <router-link to="/home"/>
      // <a href="#/home">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', {
    render(h) {
      // 找到当前的url对应的组件
      const { routeMap, current } = this.$router
      const component = routeMap[current] ? routeMap[current].component : null
      return h(component)
    }
  })
}

export default VueRouter

本文地址:https://blog.csdn.net/weixin_44385915/article/details/109562882