如何手寫簡易的 Vue Router
前言
還是那樣,懂得如何使用一個常用庫,還得了解其原理或者怎麼模擬實現,今天實現一下 vue-router 。
有一些知識我這篇文章提到了,這裡就不詳細一步步寫,請看我 手寫一個簡易的 Vuex
基本骨架
- Vue 裡面使用外掛的方式是
Vue.use(plugin)
,這裡貼出它的用法:
安裝 Vue.js 外掛。如果外掛是一個物件,必須提供 install 方法。如果外掛是一個函式,它會被作為 install 方法。install 方法呼叫時,會將 Vue 作為引數傳入。這個方法的第一個引數是 Vue 構造器,第二個引數是一個可選的選項物件。
- 全域性混入
使用 Vue.mixin(mixin)
全域性註冊一個混入,影響註冊之後所有建立的每個 Vue 例項。可以使用混入向元件注入自定義的行為,它將影響每一個之後建立的 Vue 例項。
- 路由用法
比如簡單的:
// 路由陣列 const routes = [ { path: '/',name: 'Page1',component: Page1,},{ path: '/page2',name: 'Page2',component: Page2,] const router = new VueRouter({ mode: 'history',// 模式 routes,})
它是傳入了mode
和routes
,我們實現的時候需要在VueRouter
在使用路由標題的時候是這樣:
<p> <!-- 使用 router-link 元件來導航. --> <!-- 通過傳入 `to` 屬性指定連結. --> <!-- <router-link> 預設會被渲染成一個 `<a>` 標籤 --> <router-link to="/page1">Go to Foo</router-link> <router-link to="/page2">Go to Bar</router-link> </p> <!-- 路由出口 --> <!-- 路由匹配到的元件將渲染在這裡 --> <router-view></router-view>
故我們需要使用Vue.component( id,[definition] )
註冊一個全域性元件。
瞭解了大概,我們就可以寫出一個基本骨架
let Vue = null class VueRouter { constructor(options) { this.mode = options.mode || 'hash' this.routes = options.routes || [] } } VueRouter.install = function (_Vue) { Vue = _Vue Vue.mixin({ beforeCreate() { // 根元件 if (this.$options && this.$options.router) { this._root = this // 把當前vue例項儲存到_root上 this._router = this.$options.router // 把router的例項掛載在_router上 } else if (this.$parent && this.$parent._root) { // 子元件的話就去繼承父元件的例項,讓所有元件共享一個router例項 this._root = this.$parent && this.$parent._root } },}) Vue.component('router-link',{ props: { to: { type: [String,Object],required: true,tag: { type: String,default: 'a',// router-link 預設渲染成 a 標籤 },render(h) { let tag = this.tag || 'a' return <tag href={this.to}>{this.$slots.default}</tag> },}) Vue.component('router-view',{ render(h) { return h('h1',{},'檢視顯示的地方') // 暫時置為h1標籤,下面會改 },}) } export default VueRouter
mode
vue-router
有兩種模式,預設為 hash 模式。
history 模式
通過window.history.pushStateAPI
來新增瀏覽器歷史記錄,然後通過監聽popState
事件,也就是監聽歷史記錄的改變,來載入相應的內容。
- popstate 事件
當活動歷史記錄條目更改時,將觸發 popstate 事件。如果被啟用的歷史記錄條目是通過對 history.pushState()的呼叫建立的,或者受到對 history.replaceState()的呼叫的影響,popstate 事件的 state 屬性包含歷史條目的狀態物件的副本。
- History.pushState()方法
window.history.pushState(state,title,url)
該方法用於在歷史中新增一條記錄,接收三個引數,依次為:
- state:一個與新增的記錄相關聯的狀態物件,主要用於popstate事件。該事件觸發時,該物件會傳入回撥函式。也就是說,瀏覽器會將這個物件序列化以後保留在本地,重新載入這個頁面的時候,可以拿到這個物件。如果不需要這個物件,此處可以填null。
- title:新頁面的標題。但是,現在所有瀏覽器都忽視這個引數,所以這裡可以填空字串。
- url:新的網址,必須與當前頁面處在同一個域。瀏覽器的位址列將顯示這個網址。
hash 模式
使用 URL 的 hash 來模擬一個完整的 URL。,通過監聽hashchange事件,然後根據hash值(可通過 window.location.hash 屬性讀取)去載入對應的內容的。
繼續增加程式碼,
let Vue = null class HistoryRoute { constructor() { this.current = null // 當前路徑 } } class VueRouter { constructor(options) { this.mode = options.mode || 'hash' this.routes = options.routes || [] this.routesMap = this.createMap(this.routes) this.history = new HistoryRoute() // 當前路由 this.initRoute() // 初始化路由函式 } createMap(routes) { return routes.reduce((pre,current) => { pre[current.path] = current.component return pre },{}) } initRoute() { if (this.mode === 'hash') { // 先判斷使用者開啟時有沒有hash值,沒有的話跳轉到 #/ location.hash ? '' : (location.hash = '/') window.addEventListener('load',() => { this.history.current = location.hash.slice(1) }) window.addEventListener('hashchange',() => { this.history.current = location.hash.slice(1) }) } else { // history模式 location.pathname ? '' : (location.pathname = '/') window.addEventListener('load',() => { this.history.current = location.pathname }) window.addEventListener('popstate',() => { this.history.current = location.pathname }) } } } VueRouter.install = function (_Vue) { Vue = _Vue Vue.mixin({ beforeCreate() { if (this.$options && this.$options.router) { this._root = this this._router = this.$options.router Vue.util.defineReactive(this,'_route',this._router.history) // 監聽history路徑變化 } else if (this.$parent && this.$parent._root) { this._root = this.$parent && this.$parent._root } // 當訪問this.$router時即返回router例項 Object.defineProperty(this,'$router',{ get() { return this._root._router },}) // 當訪問this.$route時即返回當前頁面路由資訊 Object.defineProperty(this,'$route',{ get() { return this._root._router.history.current },}) },}) } export default VueRouter
router-link 和 router-view 元件
VueRouter.install = function (_Vue) { Vue = _Vue Vue.component('router-link',methods: { handleClick(event) { // 阻止a標籤預設跳轉 event && event.preventDefault && event.preventDefault() let mode = this._self._root._router.mode let path = this.to this._self._root._router.history.current = path if (mode === 'hash') { window.history.pushState(null,'','#/' + path.slice(1)) } else { window.history.pushState(null,path.slice(1)) } },render(h) { let mode = this._self._root._router.mode let tag = this.tag || 'a' let to = mode === 'hash' ? '#' + this.to : this.to console.log('render',this.to) return ( <tag on-click={this.handleClick} href={to}> {this.$slots.default} </tag> ) // return h(tag,{ attrs: { href: to },on: { click: this.handleClick } },this.$slots.default) },{ render(h) { let current = this._self._root._router.history.current // current已經是動態響應 let routesMap = this._self._root._router.routesMap return h(routesMap[current]) // 動態渲染對應元件 },}) }
至此,一個簡易的vue-router就實現完了,案例完整程式碼附上:
let Vue = null class HistoryRoute { constructor() { this.current = null } } class VueRouter { constructor(options) { this.mode = options.mode || 'hash' this.routes = options.routes || [] this.routesMap = this.createMap(this.routes) this.history = new HistoryRoute() // 當前路由 // 初始化路由函式 this.initRoute() } createMap(routes) { return routes.reduce((pre,() => { this.history.current = location.pathname }) } } } VueRouter.install = function(_Vue) { Vue = _Vue Vue.mixin({ beforeCreate() { // 根元件 if (this.$options && this.$options.router) { this._root = this // 把當前vue例項儲存到_root上 this._router = this.$options.router // 把router的例項掛載在_router上 Vue.util.defineReactive(this,this._router.history) // 監聽history路徑變化 } else if (this.$parent && this.$parent._root) { // 子元件的話就去繼承父元件的例項,讓所有元件共享一個router例項 this._root = this.$parent && this.$parent._root } // 當訪問this.$router時即返回router例項 Object.defineProperty(this,methods: { handleClick(event) { // 阻止a標籤預設跳轉 event && event.preventDefault && event.preventDefault() // 阻止a標籤預設跳轉 let mode = this._self._root._router.mode let path = this.to this._self._root._router.history.current = path if (mode === 'hash') { window.history.pushState(null,path.slice(0)) } },render(h) { let mode = this._self._root._router.mode let tag = this.tag || 'a' let to = mode === 'hash' ? '#' + this.to : this.to return ( <tag on-click={this.handleClick} href={to}> {this.$slots.default} </tag> ) // return h(tag,{ render(h) { let current = this._self._root._router.history.current // current已經是動態 let routesMap = this._self._root._router.routesMap return h(routesMap[current]) // 動態渲染對應元件 },}) } export default VueRouter
ps: 個人技術博文 Github 倉庫,覺得不錯的話歡迎 star,給我一點鼓勵繼續寫作吧~
以上就是如何手寫簡易的 Vue Router的詳細內容,更多關於手寫簡易的 Vue Router的資料請關注我們其它相關文章!