前置知識
- 插件
- 混入
- Vue.observable()
- 插槽
- render函數(shù)
- 運行時和完整版的vue
首先使用vue-cli創(chuàng)建一個項目
vue create my-vue-router
新增router文件夾,在router文件夾中的index.js添加基礎(chǔ)的路由配置
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'
// 注冊插件
Vue.use(VueRouter)
const routes = [
{ path: '/', name: 'Home', component: Home},
{ path: '/about', name: 'About', component: () => import(/* webpackChuckName: "about" */ '../views/About.vue')}
]
// 創(chuàng)建路由對象
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
export default router
接下來我們再來看一個畫好的VueRouter的類圖
image.png
- options:記錄構(gòu)造函數(shù)中傳入的對象
- routeMap:用來記錄我們路由地址和組件的對應(yīng)關(guān)系, 我們會把路由規(guī)則解析到routeMap里面來
- data: 是一個對象,里面有一個current屬性,用來記錄當前路由地址,設(shè)置data的目的是因為我們需要一個響應(yīng)式的對象
- constructor幫我們初始化上面的幾個屬性
- init方法用來調(diào)用圖上所示init下面的3個方法
- initEvent方法用來注冊popstate事件,監(jiān)聽瀏覽器歷史的變化
- createRouteMap:初始化routeMap屬性,把構(gòu)造函數(shù)中傳入的路由規(guī)則轉(zhuǎn)換成鍵值對的形式存儲到routeMap里面
- initComponent: 創(chuàng)建router-link和router-view這兩個組件
實現(xiàn)思路
- 導(dǎo)出一個VueRouter的類,在類里面定義一個install靜態(tài)方法
- 判斷當前插件是否已經(jīng)被安裝
- 把VUE的構(gòu)造函數(shù)記錄到全局變量中
- 把創(chuàng)建Vue實例時傳遞的Router對象注入到所有的vue實例上
let _Vue = null
export default class VueRouter {
static install (Vue) {
// 判斷當前插件是否已經(jīng)被安裝
if (VueRouter.install.installed) {
return
}
VueRouter.install.installed = true
/*
*把VUE的構(gòu)造函數(shù)記錄到全局變量中,當前的install方法是一個靜態(tài)方法,
*在這個靜態(tài)方法中我們接收了一個參數(shù)Vue的構(gòu)造函數(shù),而將來我們在Vue-router中的一些實例方法中還要使用Vue的構(gòu)造函數(shù)
*/
_Vue = Vue
// 把創(chuàng)建Vue實例時傳遞的Router對象注入到所有的vue實例上
// 混入,
_Vue.mixin({
beforeCreate() {
if (this.$options.router) { // 實例的選項才有router,組件的選項沒有router
_Vue.prototype.$router = this.$options.router
this.$options.router.init()
}
},
})
}
}
- 創(chuàng)建constructor構(gòu)造函數(shù)
constructor (options) {
this.options = options // 記錄構(gòu)造函數(shù)中傳入的選項
this.routeMap = {} // 把options中傳入的路由規(guī)則解析出來,存儲到routeMap, 鍵:儲存的是路由地址,值:存儲的是路由組件;
this.data = _Vue.observable({ // observable: 創(chuàng)建響應(yīng)式的對象
current: window.location.pathname // 存儲當前路由地址
})
}
- createRouteMap() 遍歷所有路由規(guī)則
createRouteMap () {
// 遍歷所有的路由規(guī)則,把路由規(guī)則解析成鍵值對的形式,存儲到routeMap對象中
this.options.routes.forEach(route => {
this.routeMap[route.path] = route.component
})
}
- 創(chuàng)建initComponent創(chuàng)建組件
initComponent (Vue) {
Vue.component('router-link', {
props: {
to: String
},
// 使用運行時版本的
render (h) {// h的作用是幫我們創(chuàng)建虛擬dom
// 第一個參數(shù):選擇器,第二個參數(shù):屬性, 事件,第三個參數(shù):子元素
return h('a', {
attrs: {
href: this.to
},
on: {
click: this.clickHandler
}
}, [this.$slots.default])
},
methods: {
clickHandler (e) {
history.pushState({}, '', this.to)
this.$router.data.current = this.to
e.preventDefault()
}
}
// 需要帶編譯器版本的
// template: '<a :href="to"><slot></slot></a>'
})
const self = this
Vue.component('router-view', {
render (h) {
const component = self.routeMap[self.data.current]
return h(component)
}
})
}
- 創(chuàng)建initEvent方法用來注冊popstate事件
initEvent () {
window.addEventListener('popstate', () => {
this.data.current = window.location.pathname
})
}
- init方法調(diào)用
init () {
this.createRouteMap()
this.initComponent(_Vue)
this.initEvent()
}
完整代碼
let _Vue = null
export default class VueRouter {
// Vue.use()中調(diào)用install方法的時候會傳遞兩個參數(shù), 一個是VUE的構(gòu)造函數(shù),第二個參數(shù)是可選的選項對象
/**
* 判斷當前插件是否已經(jīng)被安裝,如果已經(jīng)安裝了就不需要重復(fù)安裝
* 把VUE的構(gòu)造函數(shù)記錄到全局變量中來,當前的install方法是一個靜態(tài)方法,在這個靜態(tài)方法中我們接收了一個參數(shù)Vue的構(gòu)造函數(shù),而將來我們在Vue-router中的一些實例方法中還要使用Vue的構(gòu)造函數(shù)
* 把創(chuàng)建Vue實例時傳遞的Router對象注入到所有的vue實例上
*/
static install (Vue) {
// 判斷當前插件是否已經(jīng)被安裝
if (VueRouter.install.installed) {
return
}
VueRouter.install.installed = true
// 把VUE的構(gòu)造函數(shù)記錄到全局變量中
_Vue = Vue
// 把創(chuàng)建Vue實例時傳遞的Router對象注入到所有的vue實例上
// _Vue.prototype.$router = this.$options.router
// 混入
_Vue.mixin({
beforeCreate() {
if (this.$options.router) { // 實例的選項才有router,組件的選項沒有router
_Vue.prototype.$router = this.$options.router
this.$options.router.init()
}
},
})
}
constructor (options) {
this.options = options // 記錄構(gòu)造函數(shù)中傳入的選項
this.routeMap = {} // 把options中傳入的路由規(guī)則解析出來,存儲到routeMap, 鍵:儲存的是路由地址,值:存儲的是路由組件;
this.data = _Vue.observable({
current: window.location.pathname // 存儲當前路由地址
})
}
init () {
this.createRouteMap()
this.initComponent(_Vue)
this.initEvent()
}
// createRouteMap()的作用是把我們構(gòu)造函數(shù)中傳過來的選項中的路由規(guī)則,存儲到routeMap對象中
createRouteMap () {
// 遍歷所有的路由規(guī)則,把路由規(guī)則解析成鍵值對的形式,存儲到routeMap對象中
this.options.routes.forEach(route => {
this.routeMap[route.path] = route.component
})
}
// initComponents
initComponent (Vue) {
Vue.component('router-link', {
props: {
to: String
},
render (h) {// h的作用是幫我們創(chuàng)建虛擬dom
// 第一個參數(shù):選擇器,第二個參數(shù):屬性, 事件,第三個參數(shù):子元素
return h('a', {
attrs: {
href: this.to
},
on: {
click: this.clickHandler
}
}, [this.$slots.default])
},
methods: {
clickHandler (e) {
history.pushState({}, '', this.to)
this.$router.data.current = this.to
e.preventDefault()
}
}
// template: '<a :href="to"><slot></slot></a>'
})
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
})
}
/**
* 總結(jié):
* 完整版本的vue包含運行時和編譯器,完整版本的vue里面的編譯器就是把我們的模版幫我們編輯成render函數(shù)
* 運行時版本的vue在組件中我們要自己來寫render函數(shù),不支持template
*/
}
最后把router配置文件的vue-router改成自己定義的文件
import VueRouter from '../vuerouter/index'
注意
- vue-cli創(chuàng)建的項目默認使用的是運行時版本的Vue.js,如果想切換成編譯器版本的Vue.js,需要修改vue-cli配置
項目的根目錄創(chuàng)建vue.config.js文件,添加下面的代碼
module.exports = { runtimeCompiler: true }