在使用vue全家桶進行開發時候,其中一個重要的插件就是vue-router,vue-router功能強大,而且使用方便,是咱們構建項目時候不可或缺的一部分,那麼vue-router究竟是怎麼實現的呢?那咱們從源碼出發,手擼一個基礎的vue-routervue
首先咱們先看一下在項目中咱們是如何使用router的git
router文件
在項目中咱們一般都會有一個router文件夾在存放咱們的router文件github
// src/router/index.js import Vue from'vue'; import VueRouter from 'vue-router'; import Page1 from'@/pages/detail1'; import Page2 from'@/pages/detail2'; // 建立VurRouter的插件 Vue.use(VueRouter); // 配置router let router = [ { path: '/', components: Page1, }, { path: '/page2', components: Page2, } ] // 實例化VurRouter,並將配置注入,導出實例 const Router = new VueRouter(router); export default Router;
vue根實例文件
在實例化和掛載vue根實例的時候,咱們會將vue-router引入到配置項vue-router
// src/main.js import Vue from 'vue'; import App from './App.vue'; import router from './router'; new Vue({ router, render: h => h(App), }).$mount('#app')
使用路由
利用路由提供的組件,咱們能夠按照咱們的需求展現和跳轉路由vuex
<template> <div id="app"> <div> <router-link to="/">page1</ router-link> <span>|</ span> <router-link to="/page2">page2</ router-link> </div> <router-view></ router-view> </ div> </ template>
閒話說了很多,下面咱們就開始手擼vue-router,let‘go!數組
實現router的思路
首先要實現router,咱們必須清楚咱們此次到底要作什麼東西,先把他們列出來:緩存
整理一下每一個組件的需求app
開始寫代碼函數
分析完需求,咱們首先來寫一下vue-router實例學習
class VueRouter { constructor(options) { // 將傳入路由配置保存在屬性$options中,方便以後調用 this.$options = options; } }
建立了實例方法,咱們開始寫install方法
// 將install中的參數Vue緩存起來,這麼作緣由有兩個: // 1.這裏的Vue實例在上面的VueRouter實例中也會用到,緩存起來能夠直接使用 // 2.由於插件是獨立的,直接引入Vue會致使打包時候把Vue打包到插件中進去,增長代碼打包後體積 var Vue; VueRouter.install = function(_Vue) { Vue = _Vue; //使用mixin和生命週期beforeCreate作全局混入, 拿到實例後的相關屬性並掛載到prototype上 Vue.mixin({ beforeCreate() { if (this.$options.router) { // 這裏能夠看出咱們爲什麼可使用this.$router拿到router實例 // 也能夠看出爲什麼要在main.js中的根實例中將router實例做爲options配置進去 Vue.prototype.$router = this.$options.router; } } }) }
接下來咱們接着完善router實例
class VueRouter { constructor(options) { // 將傳入路由配置保存在屬性$options中,方便以後調用 this.$options = options; // 這裏咱們利用hash的方法來構造路由地址,即咱們常看到的http://localhost/#/xxx // 截取url,去掉#號 this.current = window.location.hash.slice(1) ||'/'; // 利用Vue中的defineReactive方法響應式的建立一個數組matched,存放當前路由以及其嵌套路由 Vue.util.defineReactive(this, 'matched', []); // 處理路由以及嵌套路由 this.matchs(this.$options); // 監聽拿到地址並修改地址 window.addEventListener('hashchange', this.currentHash.bind(this)); // 頁面首次加載的時候也須要監聽並修改地址 window.addEventListener('load', this.currentHash.bind(this)); } currentHash() { this.current = window.location.hash.slice(1); this.matched = []; this.matchs(this.$options); } matchs(routers) { // 遞歸處理路由 // 輸出結果通常是['parent', 'child1', 'child1-child1'] routers.forEach(router => { // 通常來講/路徑下不會存放嵌套路由 if (this.current ==='/' && router.path === '/') { this.matched.push(router); } else if (this.current.includes(router.path) && router.path !== '/') { this.matched.push(router); if (router.children) { this.matchs(router.children); } } } } }
到這裏,對照需求,router的實例基本完成。
咱們接下來來寫RouterView組件
RouterView組件在實現的過程當中,咱們須要思考一個問題,就是怎麼能找到對應的RouterView視圖並匹配到對應的路由組件。這裏咱們根據matched存入的方式,引入一個概念,路由深度,表示路由的層級。
// 因爲是一個全局組件,咱們選擇寫在install中,在配置插件時候一塊兒註冊 VueRouter.install = function(_Vue) { Vue = _Vue; // 掛載router-view組件 Vue.component('RouterView', { render(h) { // 上面提到的深度標記,父組件深度爲0 let deep = 0; // 將組件標記 this.routerView = true; // 循環找出全部RouterView的父元素 並標記深度 // 利用父子通訊的中的$parent let parent = this.$parent; // 循環查找父組件,以此標記當前組件的深度,當parent不存在時候,說明已經到了頂層組件,退出循環 while(parent) { // 父組件存在且是routerView if (parent && parent.routerView) { deep+=1; } parent = parent.$parent; } // 找到匹配深度層級,找出component let matched = this.$router.matched; let _component = null; // 若是能在matched中找出,爲_component賦值 if (matched[deep]) { _component = matched[deep].components; } // 渲染_component return h(_component); } }) }
到這一步RouterView也已經完成了,咱們只剩下一個組件了
最後咱們來實現一下RouterLink組件
從需求中看,這個組件比起RouterView很簡單了,他只須要渲染一個a標籤,並展現對應文本並跳轉,和RouterView同樣,咱們在install函數中寫
VueRouter.install = function(_Vue) { Vue = _Vue; // 掛載router-link組件 Vue.component('RouterLink', { // 聲明props,to並設置爲必填 props: { to: { type: String, required: true } }, render(h) { 'a', { attrs: { href: `#${this.to}` } }, // 利用匿名插槽將組件中文本寫入 [ this.$slots.default ] } }) }
RouterLink組件功能也完成了,是否是很簡單呢?
var Vue; class VueRouter { constructor(options) { // 將傳入路由配置保存在屬性$options中,方便以後調用 this.$options = options; // 這裏咱們利用hash的方法來構造路由地址,即咱們常看到的http://localhost/#/xxx // 截取url,去掉#號 this.current = window.location.hash.slice(1) ||'/'; // 利用Vue中的defineReactive方法響應式的建立一個數組matched,存放當前路由以及其嵌套路由 Vue.util.defineReactive(this, 'matched', []); // 處理路由以及嵌套路由 this.matchs(this.$options); // 監聽拿到地址並修改地址 window.addEventListener('hashchange', this.currentHash.bind(this)); // 頁面首次加載的時候也須要監聽並修改地址 window.addEventListener('load', this.currentHash.bind(this)); } currentHash() { this.current = window.location.hash.slice(1); this.matched = []; this.matchs(this.$options); } matchs(routers) { // 遞歸處理路由 // 輸出結果通常是['parent', 'child1', 'child1-child1'] routers.forEach(router => { // 通常來講/路徑下不會存放嵌套路由 if (this.current ==='/' && router.path === '/') { this.matched.push(router); } else if (this.current.includes(router.path) && router.path !== '/') { this.matched.push(router); if (router.children) { this.matchs(router.children); } } } } } VueRouter.install = function(_Vue) { Vue = _Vue; //使用mixin和生命週期beforeCreate作全局混入, 拿到實例後的相關屬性並掛載到prototype上 Vue.mixin({ beforeCreate() { if (this.$options.router) { // 這裏能夠看出咱們爲什麼可使用this.$router拿到router實例 // 也能夠看出爲什麼要在main.js中的根實例中將router實例做爲options配置進去 Vue.prototype.$router = this.$options.router; } } }) // 掛載router-view組件 Vue.component('RouterView', { render(h) { // 上面提到的深度標記,父組件深度爲0 let deep = 0; // 將組件標記 this.routerView = true; // 循環找出全部RouterView的父元素 並標記深度 // 利用父子通訊的中的$parent let parent = this.$parent; // 循環查找父組件,以此標記當前組件的深度,當parent不存在時候,說明已經到了頂層組件,退出循環 while(parent) { // 父組件存在且是routerView if (parent && parent.routerView) { deep+=1; } parent = parent.$parent; } // 找到匹配深度層級,找出component let matched = this.$router.matched; let _component = null; // 若是能在matched中找出,爲_component賦值 if (matched[deep]) { _component = matched[deep].components; } // 渲染_component return h(_component); } }) // 掛載router-link組件 Vue.component('RouterLink', { // 聲明props,to並設置爲必填 props: { to: { type: String, required: true } }, render(h) { 'a', { attrs: { href: `#${this.to}` } }, // 利用匿名插槽將組件中文本寫入 [ this.$slots.default ] } }) } export default VueRouter;