核心代码:
Main.js:
import Vue from 'vue'
import Router from 'vue-router'
//Vue.use可以传入对象和函数
//传入函数:直接调用这个函数
//传入对象:调用该对象的install方法
Vue.use(Router)
const router = new Router({ //配置路由规则
routes:[ //配置路由
]
})
//创建vue实例并传入刚刚创建好的router对象
new Vue({
router,
render: h => h(App)
}).mount('#app')
Vue-router需要实现些什么?
带+的是对外公布的方法,带_的是静态方法
有三个属性:
- Options: 记录构造函数中传入的对象,即new VueRouter是传入的内容
- Data: 一个对象,其中有一个current属性,记录当前路由地址,data对象是响应式的,实现路由变化实时更新页面,用Vue.observe()包裹的,使其称为响应式的
- routeMap: 是一个对象,记录路由地址和组件的对应关系,将来把路由规则解析到routeMap中
方法:
- Install: 静态方法,用来实现vue的插件机制, 使用Vue.use(VueRouter)时 会自动调用该方法
- Constructor: 初始化属性
- init: 调用下面的方法
- initEvent: 注册popState事件,监听浏览器历史变化
- createRouteMap: 初始化routeMap属性,将构造函数中传入的路由规则,转换成键值对的形式传入routeMap中
- initComponent: 用来创建 router-view 和 router-link 组件
新建一个项目,实现我们自己的vue-router:
let _Vue = null
export default class VueRouter {
static install(Vue){ //接收Vue构造函数作为参数
//install方法需要做哪些事情?
//1、判断当前插件是否被安装,如果已经被安装 就不需要在安装
if(VueRouter.install.installed){
return
}
// 新建一个变量控制VueRouter是否被安装
VueRouter.install.installed = true
//2、把vue的构造函数记录到全局变量中去,因为后面会用到Vue的构造函数
_Vue = Vue
//3、把创建Vue实例时传入的router对象注入到Vue实例上
// 定义一个全局混入,全部组件都会执行这个混入
_Vue.mixin({
beforeCreate () {
// Vue实例的$options属性上有router属性,组件没有,以此来判断是否为Vue实例,Vue实例才执行这个
if(this.$options.router){
_Vue.prototype.$router = this.$options.router
// 初始化之后就调用init方法
this.$options.router.init()
}
}
})
}
constructor (options) {
//需要接收一个对象,返回值是VueRouter对象
// constructor构造函数中初始化options data routeMap属性
// 记录构造函数中传入的options
this.options = options
//路由模式
this.mode = options.mode || 'hash'
// routeMap是一个对象,解析options中传入的routes对象,存储在键值对中 键:路由地址 值:路由组件
// routerview会根据路由地址 在routerMap中找到对应的组件,将其渲染在浏览器中
this.routeMap = {}
// data是一个响应式的对象,当路由变化时 要去自动加载组件
this.data = _Vue.observable({
current: '/', //存储当前路由地址,初始化为/
})
}
createRouteMap () {
//遍历路由规则,并将路由规则以键值对的方式 存储到routeMap中
this.options.routes.forEach(route => {
this.routeMap[route.path] = route.component
})
}
init(){
this.createRouteMap()
this.initComponents(_Vue)
}
initComponents (Vue){
Vue.component('router-link', {
props: {
to: String
},
template: '<a :href="to"><slot></slot></a>'
})
}
}
在项目的router/index.js中将VueRouter引用替换成我们写的,看下效果:
import VueRouter from '../VueRouter'
运行项目:发现一个没有见过的报错内容:
大致内容为:你正在使用运行时的vue,模板编译器不可用
原因是 我们在 initComponents方法中写了template,而运行时的版本不包含模板编译器,无法将其转换成render函数,我们需要了解下vue的构建版本:
Vue的构建版本:
-
运行时版: 默认不带编译器,不支持组件中的template选项,需要打包时提前编译(把template编译成render函数)
-
完整版: 包含运行时版和编译器,体积比运行时版大10k左右,程序运行的时候吧模板转换成render函数
编译器:运行时将template模板转换成render函数
Vue-cli创建的项目默认是用运行时版
那么有两个解决方案:
一、
想要使用包含编译器的vue构建版本:需要配置vue.config.js
参考官方文档:
[https://cli.vuejs.org/zh/config/#runtimecompiler](
二、
在initComponents方法中不使用template, 使用render函数,改写一下
initComponents (Vue){
Vue.component('router-link', {
props: {
to: String
},
// template: '<a :href="to"><slot></slot></a>'
render (h) { //h函数是vue传给我们的,接收三个参数 标签名 标签的属性 生成的标签里的内容(数组)
return h('a', {
attrs: {
href: this.to
}
}, [this.$slots.default]) //this.$slots.default获取默认插槽的内容
}
})
}
完整代码:
let _Vue = null
export default class VueRouter {
static install(Vue){ //接收Vue构造函数作为参数
//install方法需要做哪些事情?
//1、判断当前插件是否被安装,如果已经被安装 就不需要在安装
if(VueRouter.install.installed){
return
}
// 新建一个变量控制VueRouter是否被安装
VueRouter.install.installed = true
//2、把vue的构造函数记录到全局变量中去,因为后面会用到Vue的构造函数
_Vue = Vue
//3、把创建Vue实例时传入的router对象注入到Vue实例上
// 定义一个全局混入,全部组件都会执行这个混入
_Vue.mixin({
beforeCreate () {
// Vue实例的$options属性上有router属性,组件没有,以此来判断是否为Vue实例,Vue实例才执行这个
if(this.$options.router){
_Vue.prototype.$router = this.$options.router
// 初始化之后就调用init方法
this.$options.router.init()
}
}
})
}
constructor (options) {
//需要接收一个对象,返回值是VueRouter对象
// constructor构造函数中初始化options data routeMap属性
// 记录构造函数中传入的options
this.options = options
//确定路由模式
this.mode = options.mode || 'hash'
// routeMap是一个对象,解析options中传入的routes对象,存储在键值对中 键:路由地址 值:路由组件
// routerview会根据路由地址 在routerMap中找到对应的组件,将其渲染在浏览器中
this.routeMap = {}
// data是一个响应式的对象,当路由变化时 要去自动加载组件
this.data = _Vue.observable({
current: '/', //存储当前路由地址,初始化为/
})
}
createRouteMap () {
//遍历路由规则,并将路由规则以键值对的方式 存储到routeMap中
this.options.routes.forEach(route => {
this.routeMap[route.path] = route.component
})
}
isHash(mode){
return mode === 'hash'
}
init(){
this.createRouteMap()
this.initComponents(_Vue)
this.initEvent()
}
initComponents (Vue){
const self = this //保存我们写的VueRouter中的this
Vue.component('router-link', { //生成router-link组件
props: {
to: String
},
// template: '<a :href="to"><slot></slot></a>'
render (h) {
return h('a', {
attrs: {
href:self.isHash(self.mode) ? '#' + this.to : this.to
},
on: { //为生成的a标签注册事件
click:this.clickHandler
}
}, [this.$slots.default])
},
methods: {
clickHandler (e) {
if(self.isHash(self.mode)){
this.$router.data.current = this.to
//hash模式是基于锚点的切换,不需要阻止默认事件
}else{
//this代表当前router-link, 同样是一个vue实例
history.pushState({}, '', this.to)
this.$router.data.current = this.to //改变data中的current属性
e.preventDefault() //阻止事件默认行为
}
}
}
})
Vue.component('router-view', { //生成router-view组件
render (h) {
//self.data.current //当前路由地址, 存在data.current中,且是响应式的
const component = self.routeMap[self.data.current]
return h(component) //h函数能帮我们将template转换成 虚拟dom
//此时 发现地址栏确实变化了,但是页面内容并没有去渲染,因为地址变化 就会去发送请求,而我们是单页面应用,找不到的
// 因此,需要使用pushHistory方法, 地址栏变化,但不去发送请求,且将地址栏变化记录下来
// 去修改router-link的默认行为
}
})
}
initEvent() { //注册浏览器popstate事件||hashchange事件,监听浏览器地址栏变化
if(this.isHash(this.mode)){
window.addEventListener('hashchange',() => {
//为data.current赋值
let hash = window.location.hash.slice(1)
this.data.current = hash
})
}else{
window.addEventListener('popstate',() => {
//获取当前路由地址, 并为data中的current赋值
this.data.current = window.location.pathname
})
}
}
}