源码分析:
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
由于在使用Router时 使用了Vue.use 并且Router为一个对象所以 Router里有一个install方法
路由的本质就是地址栏的切换渲染不同的内容
首先我们先新建一个vueRouter文件夹 在这个文件夹下新建一个index.js文件 我们对外暴露一个名字为Router的class 然后在里面写一个install方法
export default class Router{
static install(){
}
}
一 编写install 方法
install 方法是默认就加载的我们把初始化逻辑在这里写
第一步判断是否注册过插件 用变量installed来判断
第二步 把vue构造函数记录到全局变量
第三步就是混入了
想一下为什么路由在每个页面都可以使用 原因就是源码中使用了混入mixin 在beforeCreate生命周期中在每个组件实例创建之前,将路由配置添加到组件实例中,以便在组件中使用。
let _Vue = null
export default class Router{
static install(Vue){
//判断是否注册过插件
if(Router.install.installed){
return
}
Router.install.installed = true
//把vue构造函数记录到全局变量
_Vue = Vue
//混入
_Vue.mixin({
beforeCreate(){
//这里的this.$options是在Vue 2中,this.$options对象包含了创建Vue实例时传递的选项。
//当你在Vue实例中使用router选项来配置Vue Router时,它会被保存在this.$options对象中
if(this.$options.router){
//把router挂在到_Vue原型上
_Vue.prototype.$router = this.$options.router
}
}
})
}
}
二 生命变量存储路由信息和当前路由
constructor(options){
//记录options信息
this.options = options
//存储路由信息
this.routeMap = {}
//记录当前的路由 默认是/
this.data = _Vue.observable({
current:'/'
})
}
三 初始化路由 把路由信息记录在routeMap中
init(){
//初始话路由
this.createRouteMap(this.options.routes)
}
//这个方法里涉及到嵌套路由的遍历
createRouteMap(routes){
//遍历所有的路由规则包括嵌套路由 存储到routeMap中
routes.forEach(route=>{
// 判断当前路由是否有path和component
if (route.path && route.component) {
// 将path和component作为键值对存储到routeMap中
this.routeMap[route.path] = route.component;
}
// 判断当前路由是否有子路由
if (route.children && route.children.length > 0) {
// 递归遍历子路由
this.createRouteMap(route.children);
}
})
}
四 我们还需要注册router-link 和router-view 两个组件
先注册router-link router-link 实质就是一个a标签 我们需要组织默认行为 router-link 跳转的时候是根据to传的参数进行的 参数可以是String或者Object类型 我们进行如下写操作
//注册组件
initCompontent(Vue){
Vue.component('router-link',{
props:{
to:[String,Object]
},
render(h){
return h('a',{
attrs:{
href:this.resolveTo
},
on:{
click:this.clickHandler
},
},[this.$slots.default])
},
computed: {
resolveTo() {
// 判断传入的to是否是对象
if (typeof this.to === 'object') {
//这里也可以返回path自行判断
return this.to.name
} else {
// to不是对象,直接使用to作为href
return this.to;
}
},
},
methods:{
clickHandler(e){
//阻止默认事件
e.preventDefault()
if (typeof this.to === 'object') {
for (let key in this.to) {
if (this.to.hasOwnProperty(key)) {
//这里我默认写的是name去匹配 实际逻辑要多一点
history.pushState({}, '', this.to.name + this.to[key] );
this.$router.data.current = this.to.name;
return
}
}
}
//利用history.pushState去改变地址栏的路由 无刷新地向当前history插入一条历史状态
history.pushState({},'',this.to)
//渲染新的组件
this.$router.data.current = this.to
}
}
})
}
这时候你会发现在使用router-link 进行跳转时 是没有问题的 但是你点击浏览器后退或者前进时 地址栏路径变了 页面却没有渲染出来
原因就是对应的页面没有update 我们还需要监听浏览器的popstate事件
新增方法 initEvent 监听popstate事件 然后从新更新页面
initEvent(){
window.addEventListener('popstate',()=>{
//监听popstate事件 window.location.pathname获取路由地址复制给current从新渲染新的页面
this.data.current = window.location.pathname
})
}
在init里在调用事件
//初始化
init(){
this.createRouteMap(this.options.routes)
this.initCompontent(_Vue)
this.initEvent()
}
接下来我们注册一下router-view组件 在initCompontent方法里面写
//注册组件
initCompontent(Vue){
Vue.component('router-link',{
props:{
to:[String,Object]
},
render(h){
return h('a',{
attrs:{
href:this.resolveTo
},
on:{
click:this.clickHandler
},
},[this.$slots.default])
},
computed: {
resolveTo() {
// 判断传入的to是否是对象
if (typeof this.to === 'object') {
return this.to.name
} else {
// to不是对象,直接使用to作为href
return this.to;
}
},
},
methods:{
clickHandler(e){
//阻止默认事件
e.preventDefault()
if (typeof this.to === 'object') {
for (let key in this.to) {
if (this.to.hasOwnProperty(key)) {
history.pushState({}, '', this.to.name + this.to[key] );
this.$router.data.current = this.to.name;
return
}
}
}
history.pushState({},'',this.to)
this.$router.data.current = this.to
}
}
})
let self = this
Vue.component('router-view',{
render (h){
/这里只是一个简易版本 当有多个router-view时候会出问题
const compontent = self.routeMap[self.data.current]
return h(compontent)
}
})
}
完整版代码如下
let _Vue = null
export default class Router{
static install(Vue){
//判断是否注册过插件
if(Router.install.installed){
return
}
Router.install.installed = true
//把vue构造函数记录到全局变量
_Vue = Vue
//混入
_Vue.mixin({
beforeCreate(){
//这里的this.$options是在Vue 2中,this.$options对象包含了创建Vue实例时传递
//当你在Vue实例中使用router选项来配置Vue Router时,它会被保存在this.$options对象中
if(this.$options.router){
//把router挂在到_Vue原型上
_Vue.prototype.$router = this.$options.router
this.$options.router.init()
}
}
})
}
constructor(options){
//记录options信息
this.options = options
//存储路由信息
this.routeMap = {}
//记录当前的路由
this.data = _Vue.observable({
current:'/'
})
}
//初始化
init(){
this.createRouteMap(this.options.routes)
this.initCompontent(_Vue)
this.initEvent()
}
createRouteMap(routes){
//遍历所有的路由规则包括嵌套路由 存储到routeMap中
routes.forEach(route=>{
// 判断当前路由是否有path和component
if (route.path && route.component) {
// 将path和component作为键值对存储到routeMap中
this.routeMap[route.path] = route.component;
}
// 判断当前路由是否有子路由
if (route.children && route.children.length > 0) {
// 递归遍历子路由
this.createRouteMap(route.children);
}
})
}
//注册组件
initCompontent(Vue){
Vue.component('router-link',{
props:{
to:[String,Object]
},
render(h){
return h('a',{
attrs:{
href:this.resolveTo
},
on:{
click:this.clickHandler
},
},[this.$slots.default])
},
computed: {
resolveTo() {
// 判断传入的to是否是对象
if (typeof this.to === 'object') {
return this.to.name
} else {
// to不是对象,直接使用to作为href
return this.to;
}
},
},
methods:{
clickHandler(e){
//阻止默认事件
e.preventDefault()
if (typeof this.to === 'object') {
for (let key in this.to) {
if (this.to.hasOwnProperty(key)) {
history.pushState({}, '', this.to.name + this.to[key] );
this.$router.data.current = this.to.name;
return
}
}
}
history.pushState({},'',this.to)
this.$router.data.current = this.to
}
}
})
let self = this
Vue.component('router-view',{
render (h){
const compontent = self.routeMap[self.data.current]
return h(compontent)
}
})
}
initEvent(){
window.addEventListener('popstate',()=>{
this.data.current = window.location.pathname
})
}
}
然后引入直接能用