使用 vue-router 時,咱們使用 Vue.use(VueRouter)來註冊路由,而且經過 new VueRouter(routes)接收路由配置,構造路由器實例 router 並掛載到根實例選項中,使得全部組件內均可以經過 :this.$router
訪問路由器javascript
let Vue // 保存路由配置選項 class VueRouter { // new VueRouter(routes) constructor(options) { this.$options = options // todo } } VueRouter.install = function (_Vue) { // Vue = _Vue // 接受宿主環境的 Vue Vue.mixin({ beforeCreate() { // 將根組件上的 router 實例掛載到 Vue 原型,那麼全部的組件實例上都會有 $router if (this.$options.router) { Vue.prototype.$router = this.$options.router } }, }) // todo } export default VueRouter
本輪子使用hash模式,所以url中會帶有#
號。router-link
組件目的是點擊進行跳轉,能夠將思路簡化爲: <router-link to="/about">About</router-link>
=> <a href={'#'+this.to}>{this.$slots.default}</a>
上代碼:vue
const Link = Vue.extend({ props: { to: { type: String, required: true } }, render(h) { return h('a', { attrs: { href: '#' + this.to } }, [this.$slots.default] ) } }) // 註冊 router-link Vue.component('router-link', Link)
hashchange
事件,獲取到更改後的URL標識符,並在路由器中建立響應式的matched數組記錄URL變化匹配到的路由;
代碼實現以下:java
constructor(options) { // 響應式的matched 按深度存放路由配置 Vue.util.defineReactive(this, 'matched', []) // this.current 記錄的當前的URL標識符 const initPath = window.location.hash.slice(1) || '/' Vue.util.defineReactive(this, 'current', initPath) // 監聽URL變化 window.addEventListener('hashchange', this.onHashChange.bind(this)) // page加載時也要匹配當前URL須要render的組件 window.addEventListener('load', this.onHashChange.bind(this)) this.match() } onHashChange() { this.current = window.location.hash.slice(1) || '/' this.matched = [] this.match() } match(routes) { routes = routes || this.$options.routes //遞歸遍歷記錄當前URL下全部命中的route for (const route of routes) { if (route.path === '/' && this.current === '/') { // 首頁 this.matched.push(route) return } // about/info if (route.path !== '/' && ~this.current.indexOf(route.path)) { this.matched.push(route) if (route.children) { this.match(route.children) } return } } }
// 1. 標記深度 const View = Vue.extend({ render(h) { this.$vnode.data.routerView = true // 標記當前組件是 router-view let depth = 0 //遞歸確認 當前 router-view 在組件樹中的深度 let parent = this.$parent while (parent) { const vnodeData = parent.$vnode && parent.$vnode.data if (vnodeData) { if (vnodeData.routerView) { // 說明是一個 router-view ++depth } } parent = parent.$parent } let component = null const { matched } = this.$router if(matched[depth]){ component = matched[depth].component } console.log('當前深度:',depth); console.log('當前matched:',this.$router.matched); return h(component) } }) Vue.component('router-view', View)
至此,一個簡單的 mini vue-router 就實現了,咱們能夠像使用官方的vue-router同樣,引入咱們本身的 mini-vue-router在項目中使用。
固然,官方庫要遠遠複雜於本輪子,本輪子旨在理解vue-router核心思想,更深層次研究請閱讀官方源碼【》》傳送門】
本輪子實現效果以下:
node
本輪子的完整代碼:git
// 1. 是一個插件 有 VueRouter class 以及 install 方法 // 2. new VueRouter(options) 一個實例 掛載到 根實例上 而且全部組件能經過 this.$router 訪問router 實例 // 3. router-link router-view 兩個全局組件 router-link 跳轉,router-view 顯示內容 // 4. 監聽 url 變化 監聽 haschange || popstate 事件 // 5. 響應最新的 url : 建立一個響應式的屬性 current 當它改變的時候獲取對應的組件並顯示 // 6. 子組件 深度標記 與 macth() let Vue // 保存路由配置選項 class VueRouter { // new VueRouter(routes) constructor(options) { this.$options = options // todo 緩存一個路由映射表 // 響應式的matched 按深度存放路由配置 Vue.util.defineReactive(this, 'matched', []) // this.current 記錄的當前的URL標識符 const initPath = window.location.hash.slice(1) || '/' Vue.util.defineReactive(this, 'current', initPath) // 監聽URL變化 window.addEventListener('hashchange', this.onHashChange.bind(this)) // page加載時也要匹配當前URL須要render的組件 window.addEventListener('load', this.onHashChange.bind(this)) this.match() } onHashChange() { this.current = window.location.hash.slice(1) || '/' this.matched = [] this.match() } match(routes) { routes = routes || this.$options.routes //遞歸遍歷記錄當前URL下全部命中的route for (const route of routes) { if (route.path === '/' && this.current === '/') { // 首頁 this.matched.push(route) return } // about/info if (route.path !== '/' && ~this.current.indexOf(route.path)) { this.matched.push(route) if (route.children) { this.match(route.children) } return } } } } VueRouter.install = function (_Vue) { // Vue = _Vue // 接受宿主環境的 Vue Vue.mixin({ beforeCreate() { // 將根組件上的 router 實例掛載到 Vue 實例原型,那麼全部的 組件實例上都會有 $router if (this.$options.router) { Vue.prototype.$router = this.$options.router } }, }) const Link = Vue.extend({ props: { to: { type: String, required: true } }, render(h) { return h('a', { attrs: { href: '#' + this.to } }, [this.$slots.default] ) } }) // 1. 標記深度 const View = Vue.extend({ render(h) { this.$vnode.data.routerView = true // 標記當前組件是 router-view let depth = 0 //遞歸確認 當前 router-view 在組件樹中的深度 let parent = this.$parent while (parent) { const vnodeData = parent.$vnode && parent.$vnode.data if (vnodeData) { if (vnodeData.routerView) { // 說明是一個 router-view ++depth } } parent = parent.$parent } let component = null const { matched } = this.$router if (matched[depth]) { component = matched[depth].component } console.log('當前深度:', depth); console.log('當前matched:', this.$router.matched); return h(component) } }) Vue.component('router-link', Link) Vue.component('router-view', View) } export default VueRouter