如何手寫簡易的 Vue Router
前言
還是那樣,懂得如何使用一個常用庫,還得了解其原理或者怎么模擬實現(xiàn),今天實現(xiàn)一下 vue-router 。
有一些知識我這篇文章提到了,這里就不詳細(xì)一步步寫,請看我 手寫一個簡易的 Vuex
基本骨架
- Vue 里面使用插件的方式是
Vue.use(plugin),這里貼出它的用法:
安裝 Vue.js 插件。如果插件是一個對象,必須提供 install 方法。如果插件是一個函數(shù),它會被作為 install 方法。install 方法調(diào)用時,會將 Vue 作為參數(shù)傳入。這個方法的第一個參數(shù)是 Vue 構(gòu)造器,第二個參數(shù)是一個可選的選項對象。
- 全局混入
使用 Vue.mixin(mixin)
全局注冊一個混入,影響注冊之后所有創(chuàng)建的每個 Vue 實例??梢允褂没烊胂蚪M件注入自定義的行為,它將影響每一個之后創(chuàng)建的 Vue 實例。
- 路由用法
比如簡單的:
// 路由數(shù)組
const routes = [
{
path: '/',
name: 'Page1',
component: Page1,
},
{
path: '/page2',
name: 'Page2',
component: Page2,
},
]
const router = new VueRouter({
mode: 'history', // 模式
routes,
})
它是傳入了mode和routes,我們實現(xiàn)的時候需要在VueRouter構(gòu)造函數(shù)中接收。
在使用路由標(biāo)題的時候是這樣:
<p> <!-- 使用 router-link 組件來導(dǎo)航. --> <!-- 通過傳入 `to` 屬性指定鏈接. --> <!-- <router-link> 默認(rèn)會被渲染成一個 `<a>` 標(biāo)簽 --> <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 // 把當(dāng)前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 默認(rèn)渲染成 a 標(biāo)簽
},
},
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標(biāo)簽,下面會改
},
})
}
export default VueRouter
mode
vue-router有兩種模式,默認(rèn)為 hash 模式。
history 模式
通過window.history.pushStateAPI 來添加瀏覽器歷史記錄,然后通過監(jiān)聽popState事件,也就是監(jiān)聽歷史記錄的改變,來加載相應(yīng)的內(nèi)容。
- popstate 事件
當(dāng)活動歷史記錄條目更改時,將觸發(fā) popstate 事件。如果被激活的歷史記錄條目是通過對 history.pushState()的調(diào)用創(chuàng)建的,或者受到對 history.replaceState()的調(diào)用的影響,popstate 事件的 state 屬性包含歷史條目的狀態(tài)對象的副本。
- History.pushState()方法
window.history.pushState(state, title, url)
該方法用于在歷史中添加一條記錄,接收三個參數(shù),依次為:
- state:一個與添加的記錄相關(guān)聯(lián)的狀態(tài)對象,主要用于popstate事件。該事件觸發(fā)時,該對象會傳入回調(diào)函數(shù)。也就是說,瀏覽器會將這個對象序列化以后保留在本地,重新載入這個頁面的時候,可以拿到這個對象。如果不需要這個對象,此處可以填null。
- title:新頁面的標(biāo)題。但是,現(xiàn)在所有瀏覽器都忽視這個參數(shù),所以這里可以填空字符串。
- url:新的網(wǎng)址,必須與當(dāng)前頁面處在同一個域。瀏覽器的地址欄將顯示這個網(wǎng)址。
hash 模式
使用 URL 的 hash 來模擬一個完整的 URL。,通過監(jiān)聽hashchange事件,然后根據(jù)hash值(可通過 window.location.hash 屬性讀?。┤ゼ虞d對應(yīng)的內(nèi)容的。
繼續(xù)增加代碼,
let Vue = null
class HistoryRoute {
constructor() {
this.current = null // 當(dāng)前路徑
}
}
class VueRouter {
constructor(options) {
this.mode = options.mode || 'hash'
this.routes = options.routes || []
this.routesMap = this.createMap(this.routes)
this.history = new HistoryRoute() // 當(dāng)前路由
this.initRoute() // 初始化路由函數(shù)
}
createMap(routes) {
return routes.reduce((pre, current) => {
pre[current.path] = current.component
return pre
}, {})
}
initRoute() {
if (this.mode === 'hash') {
// 先判斷用戶打開時有沒有hash值,沒有的話跳轉(zhuǎn)到 #/
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) // 監(jiān)聽history路徑變化
} else if (this.$parent && this.$parent._root) {
this._root = this.$parent && this.$parent._root
}
// 當(dāng)訪問this.$router時即返回router實例
Object.defineProperty(this, '$router', {
get() {
return this._root._router
},
})
// 當(dāng)訪問this.$route時即返回當(dāng)前頁面路由信息
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', {
props: {
to: {
type: [String, Object],
required: true,
},
tag: {
type: String,
default: 'a',
},
},
methods: {
handleClick(event) {
// 阻止a標(biāo)簽?zāi)J(rèn)跳轉(zhuǎn)
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)
},
})
Vue.component('router-view', {
render(h) {
let current = this._self._root._router.history.current // current已經(jīng)是動態(tài)響應(yīng)
let routesMap = this._self._root._router.routesMap
return h(routesMap[current]) // 動態(tài)渲染對應(yīng)組件
},
})
}
至此,一個簡易的vue-router就實現(xiàn)完了,案例完整代碼附上:
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() // 當(dāng)前路由
// 初始化路由函數(shù)
this.initRoute()
}
createMap(routes) {
return routes.reduce((pre, current) => {
pre[current.path] = current.component
return pre
}, {})
}
initRoute() {
if (this.mode === 'hash') {
// 先判斷用戶打開時有沒有hash值,沒有的話跳轉(zhuǎn)到 #/
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 // 把當(dāng)前vue實例保存到_root上
this._router = this.$options.router // 把router的實例掛載在_router上
Vue.util.defineReactive(this, '_route', this._router.history) // 監(jiān)聽history路徑變化
} else if (this.$parent && this.$parent._root) {
// 子組件的話就去繼承父組件的實例,讓所有組件共享一個router實例
this._root = this.$parent && this.$parent._root
}
// 當(dāng)訪問this.$router時即返回router實例
Object.defineProperty(this, '$router', {
get() {
return this._root._router
},
})
// 當(dāng)訪問this.$route時即返回當(dāng)前頁面路由信息
Object.defineProperty(this, '$route', {
get() {
return this._root._router.history.current
},
})
},
})
Vue.component('router-link', {
props: {
to: {
type: [String, Object],
required: true,
},
tag: {
type: String,
default: 'a',
},
},
methods: {
handleClick(event) {
// 阻止a標(biāo)簽?zāi)J(rèn)跳轉(zhuǎn)
event && event.preventDefault && event.preventDefault() // 阻止a標(biāo)簽?zāi)J(rèn)跳轉(zhuǎn)
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(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, { attrs: { href: to }, on: { click: this.handleClick } }, this.$slots.default)
},
})
Vue.component('router-view', {
render(h) {
let current = this._self._root._router.history.current // current已經(jīng)是動態(tài)
let routesMap = this._self._root._router.routesMap
return h(routesMap[current]) // 動態(tài)渲染對應(yīng)組件
},
})
}
export default VueRouter
ps: 個人技術(shù)博文 Github 倉庫,覺得不錯的話歡迎 star,給我一點鼓勵繼續(xù)寫作吧~
以上就是如何手寫簡易的 Vue Router的詳細(xì)內(nèi)容,更多關(guān)于手寫簡易的 Vue Router的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
詳談Vue.js框架下main.js,App.vue,page/index.vue之間的區(qū)別
這篇文章主要介紹了詳談Vue.js框架下main.js,App.vue,page/index.vue之間的區(qū)別,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-08-08
Vue3中的shallowRef?和shallowReactive對比分析
這篇文章主要介紹了Vue3中的shallowRef?和shallowReactive,通過示例代碼逐一對他們的使用做的詳細(xì)介紹,文末補(bǔ)充介紹了vue3的shallowRef()、shallowReactive()和shallowReadonly()的使用,需要的朋友可以參考下2023-01-01
vue點擊導(dǎo)航頁面實現(xiàn)自動滾動到特定位置
這篇文章主要介紹了vue點擊導(dǎo)航頁面實現(xiàn)自動滾動到特定位置方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-03-03
在Vue3中使用BabylonJs開發(fā)?3D的初體驗
這篇文章主要介紹了在?Vue3?中使用?BabylonJs?開發(fā)?3D?是什么體驗,在本文中,向您展示了如何創(chuàng)建?Vue?組件、Babylon?類、在畫布上渲染場景以及創(chuàng)建?3D?網(wǎng)格,需要的朋友可以參考下2022-07-07

