本文旨在介紹 vue-router 的實現(xiàn)思路,并動手實現(xiàn)一個簡化版的 vue-router 。我們先來看一下一般項目中對 vue-router 最基本的一個使用,可以看到,這里定義了四個路由組件,我們只要在根 vue 實例中注入該 router 對象就可以使用了.
創(chuàng)新互聯(lián)2013年至今,先為尖山等服務(wù)建站,尖山等地企業(yè),進(jìn)行企業(yè)商務(wù)咨詢服務(wù)。為尖山企業(yè)網(wǎng)站制作PC+手機(jī)+微官網(wǎng)三網(wǎng)同步一站式服務(wù)解決您的所有建站問題。
import VueRouter from 'vue-router'; import Home from '@/components/Home'; import A from '@/components/A'; import B from '@/components/B' import C from '@/components/C' Vue.use(VueRouter) export default new VueRouter.Router({ // mode: 'history', routes: [ { path: '/', component: Home }, { path: '/a', component: A }, { path: '/b', component: B }, { path: '/c', component: C } ] })
vue-router 提供兩個全局組件, router-view
和 router-link
,前者是用于路由組件的占位,后者用于點擊時跳轉(zhuǎn)到指定路由。此外組件內(nèi)部可以通過 this.$router.push , this.$rouer.replace
等api實現(xiàn)路由跳轉(zhuǎn)。本文將實現(xiàn)上述兩個全局組件以及 push 和 replace 兩個api,調(diào)用的時候支持 params 傳參,并且支持 hash 和 history 兩種模式,忽略其余api、嵌套路由、異步路由、 abstract 路由以及導(dǎo)航守衛(wèi)等高級功能的實現(xiàn),這樣有助于理解 vue-router 的核心原理。本文的最終代碼不建議在生產(chǎn)環(huán)境使用,只做一個學(xué)習(xí)用途,下面我們就來一步步實現(xiàn)它。
install實現(xiàn)
任何一個 vue 插件都要實現(xiàn)一個 install 方法,通過 Vue.use 調(diào)用插件的時候就是在調(diào)用插件的 install 方法,那么路由的 install 要做哪些事情呢?首先我們知道 我們會用 new 關(guān)鍵字生成一個 router 實例,就像前面的代碼實例一樣,然后將其掛載到根 vue 實例上,那么作為一個全局路由,我們當(dāng)然需要在各個組件中都可以拿到這個 router 實例。另外我們使用了全局組件 router-view 和 router-link ,由于 install 會接收到 Vue 構(gòu)造函數(shù)作為實參,方便我們調(diào)用 Vue.component 來注冊全局組件。因此,在 install 中主要就做兩件事,給各個組件都掛載 router 實例,以及實現(xiàn) router-view
和 router-link
兩個全局組件。下面是代碼:
const install = (Vue) => { if (this._Vue) { return; }; Vue.mixin({ beforeCreate() { if (this.$options && this.$options.router) { this._routerRoot = this; this._router = this.$options.router; Vue.util.defineReactive(this, '_routeHistory', this._router.history) } else { this._routerRoot = (this.$parent && this.$parent._routerRoot) || this } Object.defineProperty(this, '$router', { get() { return this._routerRoot._router; } }) Object.defineProperty(this, '$route', { get() { return { current: this._routerRoot._routeHistory.current, ...this._routerRoot._router.route }; } }) } }); Vue.component('router-view', { render(h) { ... } }) Vue.component('router-link', { props: { to: String, tag: String, }, render(h) { ... } }) this._Vue = Vue; }
這里的 this 代表的就是 vue-router 對象,它有兩個屬性暴露出來供外界調(diào)用,一個是 install ,一個是 Router 構(gòu)造函數(shù),這樣可以保證插件的正確安裝以及路由實例化。我們先忽略 Router 構(gòu)造函數(shù),來看 install ,上面代碼中的 this._Vue 是個開始沒有定義的屬性,他的目的是防止多次安裝。我們使用 Vue.mixin 對每個組件的 beforeCreate 鉤子做全局混入,目的是讓每個組件實例共享 router 實例,即通過 this.$router 拿到路由實例,通過 this.$route 拿到路由狀態(tài)。需要重點關(guān)注的是這行代碼:
Vue.util.defineReactive(this, '_routeHistory', this._router.history)
這行代碼利用 vue 的響應(yīng)式原理,對根 vue 實例注冊了一個 _routeHistory 屬性,指向路由實例的 history 對象,這樣 history 也變成了響應(yīng)式的。因此一旦路由的 history 發(fā)生變化,用到這個值的組件就會觸發(fā) render 函數(shù)重新渲染,這里的組件就是 router-view 。從這里可以窺察到 vue-router 實現(xiàn)的一個基本思路。上述的代碼中對于兩個全局組件的 render 函數(shù)的實現(xiàn),因為會依賴于 router 對象,我們先放一放,稍后再來實現(xiàn)它們,下面我們分析一下 Router 構(gòu)造函數(shù)。
Router構(gòu)造函數(shù)
經(jīng)過剛才的分析,我們知道 router 實例需要有一個 history 對象,需要一個保存當(dāng)前路由狀態(tài)的對象 route ,另外很顯然還需要接受路由配置表 routes ,根據(jù) routes 需要一個路由映射表 routerMap 來實現(xiàn)組件搜索,還需要一個變量 mode 判斷是什么模式下的路由,需要實現(xiàn) push 和 replace 兩個api,代碼如下:
const Router = function (options) { this.routes = options.routes; // 存放路由配置 this.mode = options.mode || 'hash'; this.route = Object.create(null), // 生成路由狀態(tài) this.routerMap = createMap(this.routes) // 生成路由表 this.history = new RouterHistory(); // 實例化路由歷史對象 this.init(); // 初始化 } Router.prototype.push = (options) => { ... } Router.prototype.replace = (options) => { ... } Router.prototype.init = () => { ... }
我們看一下路由表 routerMap 的實現(xiàn),由于不考慮嵌套等其他情況,實現(xiàn)很簡單,如下:
const createMap = (routes) => { let resMap = Object.create(null); routes.forEach(route => { resMap[route['path']] = route['component']; }) return resMap; }
RouterHistory 的實現(xiàn)也很簡單,根據(jù)前面分析,我們只需要一個 current 屬性就可以,如下:
const RouterHistory = function (mode) { this.current = null; }
有了路由表和 history , router-view 的實現(xiàn)就很容易了,如下:
Vue.component('router-view', { render(h) { let routerMap = this._self.$router.routerMap; return h(routerMap[this._self.$route.current]) } })
這里的 this 是一個 renderProxy 實例,他有一個屬性 _self 可以拿到當(dāng)前的組件實例,進(jìn)而訪問到 routerMap ,可以看到路由實例 history 的 current 本質(zhì)上就是我們配置的路由表中的 path 。
接下來我們看一下 Router 要做哪些初始化工作。對于 hash 路由而言,url上 hash 值的改變不會引起頁面刷新,但是可以觸發(fā)一個 hashchange 事件。由于路由 history.current 初始為 null ,因此匹配不到任何一個路由,所以會導(dǎo)致頁面刷新加載不出任何路由組件?;谶@兩點,在 init 方法中,我們需要實現(xiàn)對頁面加載完成的監(jiān)聽,以及 hash 變化的監(jiān)聽。對于 history 路由,為了實現(xiàn)瀏覽器前進(jìn)后退時準(zhǔn)確渲染對應(yīng)組件,還要監(jiān)聽一個 popstate 事件。代碼如下:
Router.prototype.init = function () { if (this.mode === 'hash') { fixHash() window.addEventListener('hashchange', () => { this.history.current = getHash(); }) window.addEventListener('load', () => { this.history.current = getHash(); }) } if (this.mode === 'history') { removeHash(this); window.addEventListener('load', () => { this.history.current = location.pathname; }) window.addEventListener('popstate', (e) => { if (e.state) { this.history.current = e.state.path; } }) } }
當(dāng)啟用 hash 模式的時候,我們要檢測url上是否存在 hash 值,沒有的話強(qiáng)制賦值一個默認(rèn) path , hash 路由時會根據(jù) hash 值作為 key 來查找路由表。 fixHash 和 getHash 實現(xiàn)如下:
const fixHash = () => { if (!location.hash) { location.hash = '/'; } } const getHash = () => { return location.hash.slice(1) || '/'; }
這樣在刷新頁面和 hash 改變的時候, current 可以得到賦值和更新,頁面能根據(jù) hash 值準(zhǔn)確渲染路由。 history 模式也是一樣的道理,只是它通過 location.pathname
作為 key 搜索路由組件,另外 history 模式需要去除url上可能存在的 hash , removeHash 實現(xiàn)如下:
const removeHash = (route) => { let url = location.href.split('#')[1] if (url) { route.current = url; history.replaceState({}, null, url) } }
我們可以看到當(dāng)瀏覽器后退的時候, history 模式會觸發(fā) popstate 事件,這個時候是通過 state 狀態(tài)去獲取 path 的,那么 state 狀態(tài)從哪里來呢,答案是從 window.history 對象的 pushState 和 replaceState 而來,這兩個方法正好可以用來實現(xiàn) router 的 push 方法和 replace 方法,我們看一下這里它們的實現(xiàn):
Router.prototype.push = (options) => { this.history.current = options.path; if (this.mode === 'history') { history.pushState({ path: options.path }, null, options.path); } else if (this.mode === 'hash') { location.hash = options.path; } this.route.params = { ...options.params } } Router.prototype.replace = (options) => { this.history.current = options.path; if (this.mode === 'history') { history.replaceState({ path: options.path }, null, options.path); } else if (this.mode === 'hash') { location.replace(`#${options.path}`) } this.route.params = { ...options.params } }
pushState 和 replaceState 能夠?qū)崿F(xiàn)改變url的值但不引起頁面刷新,從而不會導(dǎo)致新請求發(fā)生, pushState 會生成一條歷史記錄而 replaceState 不會,后者只是替換當(dāng)前url。在這兩個方法執(zhí)行的時候?qū)?path 存入 state ,這就使得 popstate 觸發(fā)的時候可以拿到路徑從而觸發(fā)組件渲染了。我們在組件內(nèi)按照如下方式調(diào)用,會將 params 寫入 router 實例的 route 屬性中,從而在跳轉(zhuǎn)后的組件 B 內(nèi)通過 this.$route.params
可以訪問到傳參。
this.$router.push({ path: '/b', params: { id: 55 } });
router-link實現(xiàn)
router-view 的實現(xiàn)很簡單,前面已經(jīng)說過。最后,我們來看一下 router-link 的實現(xiàn),先放上代碼:
Vue.component('router-link', { props: { to: String, tag: String, }, render(h) { let mode = this._self.$router.mode; let tag = this.tag || 'a'; let routerHistory = this._self.$router.history; return h(tag, { attrs: tag === 'a' ? { href: mode === 'hash' ? '#' + this.to : this.to, } : {}, on: { click: (e) => { if (this.to === routerHistory.current) { e.preventDefault(); return; } routerHistory.current = this.to; switch (mode) { case 'hash': if (tag === 'a') return; location.hash = this.to; break; case 'history': history.pushState({ path: this.to }, null, this.to); break; default: } e.preventDefault(); } }, style: { cursor: 'pointer' } }, this.$slots.default) } })
router-link 可以接受兩個屬性, to 表示要跳轉(zhuǎn)的路由路徑, tag 表示 router-link 要渲染的標(biāo)簽名,默認(rèn)為標(biāo)簽。如果是 a 標(biāo)簽,我們?yōu)槠涮砑右粋€ href 屬性。我們給標(biāo)簽綁定 click 事件,如果檢測到本次跳轉(zhuǎn)為當(dāng)前路由的話什么都不做直接返回,并且阻止默認(rèn)行為,否者根據(jù) to 更換路由。 hash 模式下并且是 a 標(biāo)簽時候可以直接利用瀏覽器的默認(rèn)行為完成url上 hash 的替換,否者重新為 location.hash 賦值。 history 模式下則利用 pushState 去更新url。
以上實現(xiàn)就是一個簡單的vue-router,完整代碼參見vue-router-simple 。
總結(jié)
以上所述是小編給大家介紹的簡化版的vue-router實現(xiàn)思路詳解,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復(fù)大家的。在此也非常感謝大家對創(chuàng)新互聯(lián)網(wǎng)站的支持!
新聞名稱:簡化版的vue-router實現(xiàn)思路詳解
分享URL:http://muchs.cn/article16/picdgg.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)頁設(shè)計公司、全網(wǎng)營銷推廣、外貿(mào)建站、動態(tài)網(wǎng)站、小程序開發(fā)、營銷型網(wǎng)站建設(shè)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)