Hash模式
- URL中#后面的内容作为路径地址(改变#后面的内容,并不会向服务器发起请求,只会添加到历史记录中)
- 监听hashchange事件
- 根据当前路由地址找到对应组件重新渲染
History模式
- 通过history.pushState()方法改变地址栏(不会发起请求)
- 监听popstate事件
- 根据当前路由地址找到对应组件重新渲染
使用时的核心代码
Vue.use(VueRouter)
// Vue.use()
// 注册插件
// Vue.use() 可以传入函数和对象
// 1. 传入函数,Vue.use会直接调用这个函数
// 2. 传入对象,Vue.use会调用对象的install方法
// 由此推断,我们可以使用类实现VueRouter,添加静态方法install
const router = new VueRouter({
routes: [
{ name: 'home', path: '/', component: homeComponent }
]
})
// main.js
// 插件vue实例,注册router对象
new Vue({
router,
render: h => h(App)
}).$mount('#app')
实现 类图
三个属性
- options 记录构造函数传入的对象,传入的路由规则
- data data是一个对象,有一个current属性记录当前路由地址,设置data的目的是需要一个响应式的对象,(因为路由地址发生改变之后,对应的组件要进行相应的更新)使用vue.observable方法
- routeMap 记录路由地址和组件的关系,会把路由规则解析到routeMap中来(以键值对的方式)
方法
- 代表对外公开的方法 - 代表静态方法
- +Constructor(Options): VueRouter
- +init(): void 把需要初始化的方法集合起来一起执行
- +initEvent():void 注册popstate事件, 监听浏览器历史的变化
- +createRouteMap(): void 初始化routeMap属性的,把构造函数中传入的规则转换成键值对的形式存储到routeMap(对象)中,键是路由的对象,值是组件的地址
- +initComponents(Vue): void 用来创建routerLink,routerview组件的
- -install(Vue): void 静态方法,用于vue.use注册插件时,调用
Vue的构建版本
- 运行时版:不带编译器,不支持template模板,需要打包的时候提前编译,组件可以直接写render函数(使用render创建虚拟dom,渲染到视图)(默认版本)
- 完整版:包含运行时和编译器,体积比运行时版大10k左右,程序运行的时候把模板转换成render函数,(性能不如运行时版本)
- Vuecli默认使用运行时的vue,因为效率更高
运行时使用template报错,解决办法:
1. 使用完整版本的Vue (带有编译器等,可以使用template)
module.exports = defineConfig({
runtimeCompiler: true // 就会加载完整版本的vue
})
2. 使用运行时的Vue(需要自己写render函数)
为什么单文件组件可以使用template呢,因为打包的过程中把template编译成了render函数,这叫预编译
所以我们使用运行时的vue版本的时候,直接写render函数
// template: '<a :href="to"><slot></slot></a>'
// 相当于
render(h) {
// h 帮我们创建虚拟dom
// 接收三个参数,1. 选择器 2.给dom对象设置属性,通过attrs设置 3. slot
return h('a', {
// 设置dom对象的属性
attrs: {
href: this.to // 就是to的值
}
},
[this.$slots.default]) // 获取默认插槽的内容设置到a标签中
}
出现问题
- 点击浏览器的前进后退,组件不会切换
原因:
- popstate,当历史发送变化的时候触发,当我们调用pushState和replaceState的时候,不会触发popstate
解决办法:
- 注册popstate事件,在初始化的时候执行
完整代码
- 实现的是History模式
- 关于Vue中的options
- Vue官网的options有哪些介绍
- Vue让一个对象可响应
let _Vue = null // 记录Vue的构造函数
export default class VueRouter {
// 静态方法,Vue.use()时调用
static install(Vue) {
// 1. 判断当前插件是否已经被安装
if (VueRouter.install.installed) {
return // 表示插件已经安装
}
VueRouter.install.installed = true
// 2. 把vue的构造函数记录到全局变量中(在静态方法中接收了vue的构造函数,但是在实例中还要使用vue的构造函数,比如创建routerlink。routerview组件等)
_Vue = Vue
// 3. 把创建Vue实例时候传入的router对象注入到Vue实例上
// 因为install是静态方法,调用的时候是VueRouter.install,所以this 谁调用指向谁,此时指向的是VueRouter而不是实例
// 从选项中获取router
// _Vue.prototype.$router = this.$options.router
// 应该在能够获取到vue实例的时候写
// 混入(所有Vue的实例中都会有,组件中也有)
_Vue.mixin({
beforeCreate() {
// 此时的this,就是vue的实例
// 挂载$router只需要执行一次,而混入会执行很多次,所以需要判断一下,如果是vue组件的话就不执行了,vue实例的时候执行
// 只需要判断当前实例的$options选项中是否有router这个属性
// 因为只有Vue的$options中才有router这个属性,而组件的选项中是没有这个属性的
// $options代表的是new Vue({}),{}里面的东西就是options https://cn.vuejs.org/v2/api/#%E9%80%89%E9%A1%B9-%E6%95%B0%E6%8D%AE
if (this.$options.router) {
// 代表是Vue实例
_Vue.prototype.$router = this.$options.router
// 初始化中调用 先找到router对象(此时相当于找到这个类,然后执行类下面的方法),然后调用init方法
this.$options.router.init()
}
}
})
}
constructor(options) {
this.options = options // 记录构造函数传入的对象,传入的路由规则
this.routeMap = {} // 把options传入的路由规则解析出来,存储到routeMap中,键是路由组件,值是路由地址
this.data = _Vue.observable({
current: '/'
}) // 创建响应式对象,可用于计算属性和方法中 记录当前路由地址
}
// 把下面两个方法整合
init() {
this.createRouteMap()
this.initComponents(_Vue)
this.initEvent()
}
// 把构造函数中传过来的选项规则,转换成键值对的方式,存储到routeMap中,键是路由组件,值是路由地址,发生变化时,根据地址找到对应的组件,渲染到视图上
createRouteMap() {
// 作用:遍历所有路由规则,把路由规则解析成键值对的形式,存储到routeMap中
this.options.routes.forEach(route => {
this.routeMap[route.path] = route.component
})
}
// 可传可不传,因为已经记录了,传的目的是 减少方法和外部的依赖
initComponents(Vue) {
// 创建router-lint,最终是渲染成一个超链接
// 路由名字,组件的选项
Vue.component('router-link', {
// 接收外部传入的to
props: {
to: String
},
// 完整版本的Vue(带有编译器)支持template模板
// 将来会把router-link之间的内容填到slot插槽中
// template: '<a :href="to"><slot></slot></a>'
// 使用运行时的Vue,不支持template模板
// h函数是vue传的
render(h) {
// h的作用 帮我们创建虚拟dom
// h函数接收三个参数,1. 选择器 2.给dom对象设置属性,通过attrs设置 3. slot
return h('a', {
// 设置dom对象的属性
attrs: {
href: this.to // 就是to的值
},
on:{
//给click注册点击事件,不需要加()
click: this.clickHandler
}
},
[this.$slots.default]
) // 获取默认插槽的内容设置到a标签中数组中
},
methods: {
clickHandler(e){
// 传入的参数,名字,跳转的位置
history.pushState({},'',this.to)
// 找到路由东西,之前注册插件的时候,已经把$router挂载到了Vue构造函数的原型属性上,所以所有的实例都能访问到$router
this.$router.data.current = this.to
e.preventDefault() // 阻止默认事件
}
}
})
const self = this // 此时的this是VueRouter的实例,在h函数中拿不到VueRouter的实例,所以在外部标识一下
// 创建router-view, 相当于一个占位符,在组件内部,根据当前路由地址,获取到路由组件,并渲染到router-view位置
Vue.component('router-view', {
render(h) {
// 1. 找到当前路由的地址
// 2. 根据当前地址去routeMap中找到对应的组件
// 3. 调用h函数帮我们转换成虚拟dom,返回,h函数可以直接把一个组件转换成虚拟dom
// 此时的this,是在render方法内,已经不是VueRouter的实例了
// self.data.current
const component = self.routeMap[self.data.current]
// 不让超链接跳转,不希望向服务端发,给超链接注册点击事件,不让他跳转,修改地址栏中的地址
// 使用history.pushState,不会向服务器发送请求,修改地址栏中的地址,把当前地址记录到current,
// h函数帮我们创建虚拟dom,返回虚拟dom
return h(component)
}
})
}
// popstate是当历史发送变化的时候触发,调用pushState,replaceState的时候是不会触发的
// 注册popstate事件
initEvent(){
window.addEventListener('popstate',() => {
// 把当前地址栏的地址取出来,/后面的
// 此时的this是initEvent,也就是当前VueRouter的对象
this.data.current = window.location.pathname
})
}
}