理解
隨著前端業(yè)務(wù)的發(fā)展,
我們一般在寫一個(gè)較為大型的vue項(xiàng)目時(shí)候,會(huì)使用到vue-router,來根據(jù)指定的url或者hash來進(jìn)行內(nèi)容的分發(fā),可以達(dá)到不像服務(wù)端發(fā)送請(qǐng)求,就完成頁(yè)面內(nèi)容的切換,能夠減少像服務(wù)器發(fā)送的請(qǐng)求,讓用戶進(jìn)行頁(yè)面跳轉(zhuǎn)時(shí)候能夠更快,體驗(yàn)更好
疑問
在初學(xué)vue-router的時(shí)候,一般人都會(huì)有一個(gè)印象,router-link以及router-view都是vue原生自帶的標(biāo)簽。但是這個(gè)印象是錯(cuò)誤的,vue-router本質(zhì)上是一個(gè)vue的插件,通過Vue.use(VueRouter)來使用這個(gè)插件。router-link以及router-view也是這個(gè)插件實(shí)現(xiàn)的自定義標(biāo)簽。
本文以開發(fā)插件的模式,擼一個(gè)vue-router插件以加深對(duì)其原理的了解
url變化流程圖解

也就是說,要實(shí)現(xiàn)一個(gè)簡(jiǎn)單的vue-router,需要完成以下需求
具體操作
創(chuàng)建vue項(xiàng)目
vue create my-vue-router
由于只著重于vue-router的內(nèi)容,所以先使用原本的vue-router這樣只替換vue-router源碼文件即可
增加vue-router
vue add router
然后項(xiàng)目目錄就變成了
my-vue-router
|- node_modules
|- public
|- src
|- assets
|- components
|- HellowWorld.vue
|- router
|- index.js
|- views
|- About.vue
|- Home.vue
|- App.vue
|- main.js
|- .gitinore
|- babel.config.js
|- package.json
|- README.md
|- yarn.lock
在目錄中,新建一個(gè)myRouter.js的文件,來放置我們的源碼
新建自己的myRouter文件
my-vue-router
|- node_modules
|- public
|- src
|- assets
|- components
|- HellowWorld.vue
|- router
|- index.js
+ |- myRouter.js
|- views
|- About.vue
|- Home.vue
|- App.vue
|- main.js
|- .gitinore
|- babel.config.js
|- package.json
|- README.md
|- yarn.lock
切換引入文件為自己寫的myRouter.js
此時(shí),@/src/router/index.js中的內(nèi)容里,我們將導(dǎo)入的vue-router替換為我們的myRouter.js
import Vue from 'vue'
- import VueRouter from 'vue-router'
+ import VueRouter from './myRouter'
import Home from '../views/Home.vue'
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
}
]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
export default router
這里我們可以看到,代碼執(zhí)行的流程為
引入myRouter.js->配置routes對(duì)象->new VueRouter->export default導(dǎo)出
此處用到了 Vue.use()這個(gè)API
Vue.use()
vue中的插件,一個(gè)核心的api就是vue.use()
安裝 Vue.js 插件。如果插件是一個(gè)對(duì)象,必須提供 install 方法。如果插件是一個(gè)函數(shù),它會(huì)被作為install 方法。install 方法調(diào)用時(shí),會(huì)將 Vue 作為參數(shù)傳入。
該方法需要在調(diào)用 new Vue() 之前被調(diào)用。
當(dāng) install 方法被同一個(gè)插件多次調(diào)用,插件將只會(huì)被安裝一次。
也就是說,我們?cè)谧约涸斓?code>myRouter里得實(shí)現(xiàn)這個(gè)install方法
需求
- 提供一個(gè)構(gòu)造類,能夠使用
new VueRouter來生成實(shí)例 - 實(shí)現(xiàn)install方法
- 監(jiān)聽
url變化,并雙向綁定current方法 - 注冊(cè)自定義組件
router-link與router-view - 實(shí)現(xiàn)用戶配置的路由數(shù)組到map的轉(zhuǎn)換,方便快速的查詢到路由匹配的對(duì)象
實(shí)現(xiàn)
let Vue;//由于使用者肯定是使用vue.use引入的這個(gè)插件,所以代碼里就不引入vue.js了,防止重復(fù)打包
// 需求1 聲明一個(gè)擁有constructor構(gòu)造器的class
class VueRouter{
constructor(options={}){// 構(gòu)造函數(shù)
this.$options=options;// 保存配置項(xiàng)
this.app = { // 聲明一個(gè)擁有current的變量,已完成路由的雙向綁定
current:"/"
}
Vue.util.defineReactive(this.app,'current',this.app.current);//vue的攔截方法,會(huì)該值增加get攔截以收集依賴,set攔截以觸發(fā)雙向綁定
this.routerMap={}; // 創(chuàng)建key-value模式的routerMap,便于使用key能夠快速的找到即將render(渲染)的組件
this.init(options); // 執(zhí)行init方法,以完成需求3,4,5
}
init(options={}){
this.bindBrowserEvents()// 綁定瀏覽器事件
this.initComponent()//注冊(cè)router-view及router-link組件
this.createRouterMap(options.routes)//創(chuàng)建key-value模式的routerMap
}
createRouterMap(arr=[]){ // 創(chuàng)建routerMap
arr.forEach(item => {
this.routerMap[item.path]=item
});
// 處理完后routerMap格式如下
// this.routerMap = {
// '/':{
// path: '/',
// name: 'Home',
// component: Home
// },
// '/about':{
// path: '/about',
// name: 'About',
// component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
// }
// }
}
bindBrowserEvents(){ // hash模式監(jiān)聽 hashchange 方法
window.addEventListener('load',this.onHashChange.bind(this))
window.addEventListener('hashchange',this.onHashChange.bind(this))
}
initComponent(){ // 注冊(cè)自定義組件RouterLink及RouterView
Vue.component('RouterLink',{
props: {
to: String
},
render(h) {
return h('a',{
attrs:{
href:'#'+this.to
}
},this.$slots.default)
},
})
Vue.component('RouterView',{
render:(h)=>{
const component = this.routerMap[this.app.current].component
return h(component)
},
})
}
onHashChange(){ // hash變化時(shí),改變 this.app.current
window.location.hash = window.location.hash || '/'; // 如果hash沒有值,則默認(rèn)給補(bǔ)一個(gè)/#/
if(this.routerMap[window.location.hash.slice(1)]){ // this.app.current = hash值
this.app.current = window.location.hash.slice(1);
}else{
this.app.current = '/';
}
// 此處執(zhí)行完后,則由于雙向綁定,會(huì)觸發(fā)routerView進(jìn)行重新渲染
}
}
// 需求2 實(shí)現(xiàn)install方法
VueRouter.install = function(_Vue){
Vue = _Vue; // 因?yàn)橐欢〞?huì)先走install,所以將這個(gè)傳入的Vue實(shí)例,保存到變量Vue中
}
注釋都寫在代碼里啦,可以執(zhí)行簡(jiǎn)單的路由雙向綁定功能,有哪里有疑問可以提出互相學(xué)習(xí)
覺得好的話,可以給我的 github點(diǎn)個(gè)star哦