前言
正如你所了解到的Vue Router是Vue.js官方的路由管理器,它可以让构建单页面应用变得更加简单。与之离不开的还有vue-view即路由容器以及路由跳转用到的router-link标签等。
接下来让我们来看下Vue Router是如何实现的?
抛砖引玉
平时我们使用Vue Router的方式一般是在router文件夹下的index.js文件找中先引入,然后在注册,如下:
import Vue from 'vue'
import Router from 'router'
Vue.use(Router)
由上可知:其实
Vue Router是一个插件
引入注册之后,就是创建路由实例,并写入路由表,如下:
export default new Router({
mode: 'hash',
routes: [...]
})
再之后就是在main.js里把创建的路由实例挂载到Vue实例上,如下:
import Vue from 'vue'
import App from './App'
import router from './router'
new Vue({
el: '#app',
router,
components: { App },
template: '<App/>'
})
这一步比较重要,就是为了把
router挂载全局上,可以让每个组件都可以使用,即Vue.prototype.$router = router。
到这里Vue Router的引入、注册和挂载三步就算完成了,瞅一瞅下面这张图:
问题:
1、router-view是路由出口,是怎么替换内容的?
2、router-link和router-viwe为什么可以直接使用?
聪明的小伙伴可能想到了,就是在注册挂载Vue router的时候对这俩组件进行了声明和注册了,具体怎么实现的呢?
解开源码的面纱
现在开始写咱们自己的Vue Router暂且起名为MyRouter吧。既然Vue Router是一个组件,那么我们就要MyRouter中实现一个install方法,大体结构如下:
let Vue;
class MyRouter {
constructor(options) {
this.$options = options;
this.current = '/';
// 路由映射表
this.routeMap = {};
options.routes.forEach(router => {
this.routeMap[router.path] = router;
});
}
}
MyRouter.install = function(_Vue) {
// 保存构造函数,以便在 MyRouter 里面使用
Vue = _Vue;
}
export default MyRouter;
问题:怎么才能获取到根实例中的
router选项呢?
答案就是每个组件中都混入一个生命周期,来获得router选项,如下:
MyRouter.install = function(_Vue) {
// 保存构造函数,以便在 MyRouter 里面使用
Vue = _Vue;
// 混入的目的:延迟下面的逻辑到router创建完毕并且附加到选项时才执行
_Vue.mixin({
beforeCreate() {
// 获得根实例中的router选项:只有根实例才有此选项
if (this.$options.router) {
Vue.prototype.$router = this.$options.router;
}
}
})
}
上面提到:router-link和router-view两个组件也是在注册router时声明并注册的,所以接下来我们要在install方法里分别实现这俩组件的注册。
router-link
_Vue.component('router-link',{
props:{
to: {
type: String,
required: red
}
},
render(h){
return h('a', {attrs:{href:'#' + this.to}}, this.$slots.default)
}
})
注意
1、不能使用tamplate方式来注册组件,因为是在运行时版本不存在编译器,所以只能使用render函数的方式。
2、可以用this.$slots.default的方式拿到标签的HTML内容。
router-view
_Vue.component('router-view',{
render(h){
const { routeMap, current } = this.$router;
const component = routeMap[current].component || null;
return h(component)
}
})
install 完整代码如下
MyRouter.install = function(_Vue) {
// 保存构造函数,以便在 MyRouter 里面使用
Vue = _Vue;
// 混入的目的:延迟下面的逻辑到router创建完毕并且附加到选项时才执行
_Vue.mixin({
beforeCreate() {
// 获得根实例中的router选项:只有根实例才有此选项
if (this.$options.router) {
Vue.prototype.$router = this.$options.router;
}
}
})
// 注册 router-link 组件
_Vue.component('router-link', {
props: {
to: {
type: String,
required: red
}
},
render(h) {
return h('a', { attrs: { href: '#' + this.to } }, this.$slots.default)
}
});
// 注册 router-view 组件
_Vue.component('router-view', {
render(h) {
const { routeMap, current } = this.$router;
const component = routeMap[current].component || null;
return h(component)
}
})
}
组件注册完成后就开始检测Url变化了,该怎么检测呢?你可能想到了就是用addEventlistener去监听hashchange事件,如下:
class MyRouter {
constructor(options) {
this.$options = options;
this.current = '/';
// 路由映射表
this.routeMap = {};
options.routes.forEach(router => {
this.routeMap[router.path] = router;
});
// 监听 Url 变化
window.addEventListener('hashchange', () => {
this.current = window.location.hash.slice(1);
})
}
}
问题:测试我们的代码可以发现页面是出来了,但切换路由时页面并没有切换?
因为Url并没有做到响应式处理,这里需要用到Vue提供的帮助方法库util,如下:
class MyRouter {
constructor(options) {
this.$options = options;
// 需要创建响应式的 curren 属性
// 这样将来currnt变化的时候,依赖的组件会重新rander
Vue.util.defineReactive(this, 'current', '/');
// 监听 Url 变化
window.addEventListener('hashchange', () => {
this.current = window.location.hash.slice(1);
})
}
}
这时再切换路由,发现可以正常访问页面了~~
问题:我们创建响应式
current时给的默认值是'/',也就是说如果当前页面不是'/'而是'/about'的话,一刷新就会跳转至'/'页面,该如何避免呢?
解决起来也很简单,就是再监听一下load事件,如下:
class MyRouter {
constructor(options) {
this.$options = options;
// 路由映射表
this.routeMap = {};
options.routes.forEach(router => {
this.routeMap[router.path] = router;
});
// 需要创建响应式的 curren 属性
// 这样将来currnt变化的时候,依赖的组件会重新rander
Vue.util.defineReactive(this, 'current', '/');
// 监听 Url 变化
window.addEventListener('hashchange', this.onHashChange.bind(this));
// 重新加载事件
window.addEventListener('load', this.onHashChange.bind(this));
}
onHashChange() {
this.current = window.location.hash.slice(1);
}
}
这时再刷新页面就可以正常展示了,nice ~~~ 大概的思路就是这样了,希望对你有帮助,欢迎评论留言 ~~~