还是那样,懂得如何使用一个常用库,还得了解其原理或者怎么模拟实现,今天实现一下 vue-router。
有一些知识我这篇文章提到了,这里就不详细一步步写,请看我 手写一个简易的 Vuex
基本骨架
- Vue 里面使用插件的方式是
Vue.use(plugin),这里贴出它的用法:
安装 Vue.js 插件。如果插件是一个对象,必须提供 install 方法。如果插件是一个函数,它会被作为 install 方法。install 方法调用时,会将 Vue 作为参数传入。这个方法的第一个参数是 Vue 构造器,第二个参数是一个可选的选项对象。
使用 Vue.mixin(mixin)
全局注册一个混入,影响注册之后所有创建的每个 Vue 实例。可以使用混入向组件注入自定义的行为,它将影响每一个之后创建的 Vue 实例。
比如简单的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| const routes = [ { path: '/', name: 'Page1', component: Page1, }, { path: '/page2', name: 'Page2', component: Page2, }, ]
const router = new VueRouter({ mode: 'history', routes, })
|
它是传入了mode和routes,我们实现的时候需要在VueRouter构造函数中接收。
在使用路由标题的时候是这样:
1 2 3 4 5 6 7 8 9 10
| <p> <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])注册一个全局组件。
了解了大概,我们就可以写出一个基本骨架
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| 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 this._router = this.$options.router } else if (this.$parent && this.$parent._root) { this._root = this.$parent && this.$parent._root } }, })
Vue.component('router-link', { props: { to: { type: [String, Object], required: true, }, tag: { type: String, default: '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', {}, '视图显示的地方') }, }) }
export default VueRouter
|
mode
vue-router有两种模式,默认为 hash 模式。
history 模式
通过window.history.pushStateAPI 来添加浏览器历史记录,然后通过监听popState事件,也就是监听历史记录的改变,来加载相应的内容。
当活动历史记录条目更改时,将触发 popstate 事件。如果被激活的历史记录条目是通过对 history.pushState()的调用创建的,或者受到对 history.replaceState()的调用的影响,popstate 事件的 state 属性包含历史条目的状态对象的副本。
1
| window.history.pushState(state, title, url)
|
该方法用于在历史中添加一条记录,接收三个参数,依次为:
1 2 3
| state:一个与添加的记录相关联的状态对象,主要用于popstate事件。该事件触发时,该对象会传入回调函数。也就是说,浏览器会将这个对象序列化以后保留在本地,重新载入这个页面的时候,可以拿到这个对象。如果不需要这个对象,此处可以填null。 title:新页面的标题。但是,现在所有浏览器都忽视这个参数,所以这里可以填空字符串。 url:新的网址,必须与当前页面处在同一个域。浏览器的地址栏将显示这个网址。
|
hash 模式
使用 URL 的 hash 来模拟一个完整的 URL。,通过监听hashchange事件,然后根据hash值(可通过 window.location.hash 属性读取)去加载对应的内容的。
继续增加代码,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
| 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') { 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 { 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) } else if (this.$parent && this.$parent._root) { this._root = this.$parent && this.$parent._root } Object.defineProperty(this, '$router', { get() { return this._root._router }, }) Object.defineProperty(this, '$route', { get() { return this._root._router.history.current }, }) }, }) }
export default VueRouter
|
router-link 和 router-view 组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| 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) { 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已经是动态响应 let routesMap = this._self._root._router.routesMap return h(routesMap[current]) // 动态渲染对应组件 }, }) }
|
至此,一个简易的vue-router就实现完了,案例完整代码附上:mini-vue-router