使用 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