一、Vue Router实现原理
Vue Router 基础回顾
Hash模式和History模式
模拟实现自己的Vue Router
1.基本使用
引入路由相关的组件
通过Vue.use注册路由插件
创建路由的对象,配置路由规则
注册路由对象
通过routerview前去占位
routerlink去跳转
动态路由
this.$route.push(/replace/go的方法)
- Hash和History模式的区别
- Hash
- music.163.com/#/playlist?…
- hash模式背后的原理是
onhashchange事件,可以在window对象上监听这个事件,以html锚点作为路由地址
- History
- music.163.com/playlist/xx…
- History模式是基于Html5中的HistoryAPI
- history.pushState()
- history.replaceState()
- 怕刷新
- History需要服务器的支持
- 单页应用中,服务器端不存在www.testurl.com/login这样的地址会…
- 再服务端应该除了静态资源外都返回单页应用的index.html
- Hash
2.实现原理
2.1前置知识
- 插件
- 混入
- Vue.observable()
- 插槽
- render函数
- 运行时和完整般的Vue
- Hash模式
- Url中#后面的内容作为路径地址,当仅改变#号后的内容,浏览器不会向服务器请求,但是会把这个路径存放在历史记录里面
- #号后的内容发生改变的时候,监听hashchange事件
- 根据当前路由地址找到对应的组件重新渲染
- History模式
- history.pushState()方法改变地址栏目,并把路径存放再历史记录中
- 监听popstate事件,记录改变地址,当前进后退或则back方法事件才会被触发
- 监听当前路由地址找到对应的组件重现渲染
- 类图

- options:作用是记录构造函数中传入的对象, 我们在创建Vue Router的实例的时候,传递了一个对象,而该对象中定义了路由规则。而options就是记录传入的这个对象的。
- routeMap:是一个对象,记录路由地址与组件的对应关系,也就是一个键值对的形式,后期会options中路由规则解析到routeMap中。
- data是一个对象,该对象中有一个属性current,该属性用来记录当前的路由地址,data是一个响应式的对象,因为当前路由地址发生变化后,对应的组件要发生更新(也就说当地址变化后,要加载对应组件)。
- install是一个静态方法,用来实现Vue的插件机制。
- Constructor是一个构造方法,该该构造方法中会初始化options ,data,routeMap这几个属性。
- init方法主要是用来调用下面的三个方法,也就把不同的代码分隔到不同的方法中去实现
- initEvent方法,用来注册popstate事件,
- createRouteMap方法,该方法会把构造函数中传入进来的路由规则,转换成键值对的形式存储到routeMap中。 键就是路由的地址,值就是对应的组件
- initComponents方法,主要作用是用来创建router-link和router-view这两个组件的。
2.2 Vue-router install
- 知识补充
- vue.use
- 安装 Vue.js 插件。如果插件是一个对象,必须提供
install方法。如果插件是一个函数,它会被作为 install 方法。install 方法调用时,会将 Vue 作为参数传入
- 安装 Vue.js 插件。如果插件是一个对象,必须提供
- vue.mixin
- 混入 (mixins) 是一种分发vue组件中可复用功能的非常灵活的方式。混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被混入该组件本身的选项。
- 钩子函数合并同名钩子函数将混合为一个数组,因此都将被调用。另外,混入对象的钩子函数将在组件自身钩子函数之前调用
- 数据对象合并 数据对象在内部会进行浅合并 (一层属性深度),在和组件的数据发生冲突时以组件数据优先(组件的data中变量会覆盖混入对象的data中变量)
- 普通方法合并当混合值为对象的选项时,例如 methods、components、directive,将被混合为同一个对象,两个对象键名冲突时,取组件对象的键值对
- 混入可以全局和局部
- vue.$options
- 用于当前 Vue 实例的初始化选项。
- vue.use
let _Vue = null
export default class VueRouter{
static install(Vue) {
// 1.install判断当前插件是否已经被安装
// 在私有方法上定义个标识,开始的时候没有,然后当存在的是侯就代表安装过
if(VueRouter.install.installed){
return
}
VueRouter.install.installed = true
// 2.把vue的构造照函数定义到全局变量中来,后面实例方法需要使用到这个构造函数
_Vue = Vue
// 3.把创建vue实例时传入的router对象,注入到vue的实例上
// 可以通过混入来给vue实例中添加这个$router方法
_Vue.mixin({
beforeCreate (){
// 判断是否有这个属性,
if (this.$options.router){
_Vue.prototype.$router = this.$optiongs.router
}
}
})
}
}
2.3 构造函数
该构造方法中会初始化options ,data,routeMap这几个属性。
options作用是记录构造函数中传入的对象, 我们在创建Vue Router的实例的时候,传递了一个对象,而该对象中定义了路由规则和一些配置选项。routeMap:是一个对象,记录路由地址与组件的对应关系,也就是一个键值对的形式,实际上是根据我们传入的routes,经行一个数据处理,以后当我们使用的时候就可以根据地址找到对应组件data是一个对象,该对象中有一个属性current,该属性用来记录当前的路由地址,data是一个响应式的对象,因为当前路由地址发生变化后,对应的组件要发生更新(也就说当地址变化后,要加载对应组件)。
知识补充
- Vue.observable( object )
- 让一个对象可响应。Vue 内部会用它来处理
data函数返回的对象。返回的对象可以直接用于渲染函数和计算属性内,并且会在发生变更时触发相应的更新。
- 让一个对象可响应。Vue 内部会用它来处理
constructor(options){
this.options = options;//记录构造函数中传入的选项
this,routeMap ={};
this.data = _Vue.observable({
current: "/",
});
}
2.4 createRouteMap()
createRouteMap方法,该方法会把构造函数中传入进来的options参数中的路由规则(routes),转换成键值对的形式存储到routeMap中。 键就是路由的地址,值就是对应的组件
createRouteMap(){
// 遍历所有地址,把路由规则接新城键值对的形式,存储到routeMap中
this.options.routes.map( route => {
this.routeMap[route.path] = route.component
})
}
2.5 initComponents()
-
initComponents方法,主要作用是用来创建router-link和router-view这两个组件的。 -
createRouteMap()和initComponents()在实例注册的时候挂载 -
需要手写自己的render函数生成对应的a 标签
-
a标签需要跳转到对应路由(注意式不刷新页面,并且改变地址)
-
router-view通过响应式的地址指向对应的vue组件
initComponents(Vue) {
//传入有Vue的构造函数,减少类对外部的依赖
//创建router——link组件,router——link最终会被渲染成一个标签
Vue.component('router-link', {
props: {
to: String
},
// template: '<a :href = "to"><slot></slot></a>'
render(h){
return h('a',{
attrs: {
href: this.to
},
on:{
click:this.handleClick
}
},[this.$slots.default])
},
methods:{
handleClick(e){
// 改变地址栏
history.pushState({},'',this.to)
// 注意这里的this,指向的这个组件,通过找到这个构造数的原型上的响应式的data.current
this.$router.data.current = this.to
// 这里引入事件对象,并且组织a标签的默认行为
e.preventDefault()
}
}
})
// 实现router-view组件
// self指向一个vue-router实例
const self = this
Vue.component('router-view', {
// 创建并返回一个虚拟dom
render(h){
// 通过地址找到对应的组件
const component = self.routeMap[self.data.current]
return h(component)
}
})
}
2.6 initEvent()
- 该事件用来解决浏览器的前进及后退
- popstate
- 当活动历史记录条目更改时,将触发popstate事件。如果被激活的历史记录条目是通过对history.pushState()的调用创建的,或者受到对history.replaceState()的调用的影响,popstate事件的state属性包含历史条目的状态对象的副本。
// initEvent 事件用来处理popstates,注意在init方法中调用
initEvent() {
window.addEventListener('popstate' , () => {
//注意的式这里this直线vue的实例
this.data.current = window.location.pathname
})
}
3.完整代码
let _Vue = null
export default class VueRouter {
static install(Vue) {
if (VueRouter.install.installed) {
return
}
VueRouter.install.installed = true
_Vue = Vue
_Vue.mixin({
beforeCreate() {
if (this.$options.router) {
_Vue.prototype.$router = this.$options.router
this.$options.router.init()
}
}
})
}
constructor(options) {
this.options = options;
this.routeMap = {};
this.data = _Vue.observable({
current: "/",
});
}
init() {
this.createRouteMap()
this.initComponents(_Vue)
this.initEvent()
}
createRouteMap() {
this.options.routes.map(route => {
this.routeMap[route.path] = route.component
})
}
initComponents(Vue) {
Vue.component('router-link', {
props: {
to: String
},
render(h){
return h('a',{
attrs: {
href: this.to
},
on:{
click:this.handleClick
}
},[this.$slots.default])
},
methods:{
handleClick(e){
history.pushState({},'',this.to)
this.$router.data.current = this.to
e.preventDefault()
}
}
})
const self = this
Vue.component('router-view', {
render(h){
const component = self.routeMap[self.data.current]
return h(component)
}
})
}
initEvent() {
window.addEventListener('popstate' , () => {
this.data.current = window.location.pathname
})
}
}