需求分析
- 作为一个插件存在:实现VueRouter类和install方法
- 实现两个全局组件:router-view用于显示匹配组件内容,router-link用于跳转
- 监控url变化:监听hashchange或popstate事件
- 响应最新url:创建一个响应式的属性current,当它改变时获取对应组件并显示
实现
- 作为一个插件存在
// cvue-router.js
let Vue;
// 1. 实现一个插件:挂载$router
class CVueRouter {
constructor(options) {
this.$options = options;
}
}
CVueRouter.install = function(_Vue) {
// 保存构造函数在KvueRouter里使用
Vue = _Vue;
// 挂载$router
// 获取根实例中的router选项
Vue.mixin({
beforeCreate() {
// 确保在根实例上挂载
if (this.$options.router) {
Vue.prototype.$router = this.$options.router;
}
}
})
}
export default CVueRouter;
- 监控url变化
// cvue-router.js
class CVueRouter {
constructor(options) {
this.$options = options;
// 需要创建响应式的current属性
Vue.util.defineReactive(this, 'current', '/');
// this.current = '/';
// 监控url变化
window.addEventListener('hashchange', this.onHashChange.bind(this));
window.addEventListener('load', this.onHashChange.bind(this));
}
onHashChange() {
console.log(window.location.hash)
this.current = window.location.hash.slice(1);
}
}
- 实现两个全局组件:router-view用于显示匹配组件内容,router-link用于跳转
CVueRouter.install = function(_Vue) {
//...
Vue.component('router-link', {
props: {
to: {
type: String,
required: true
},
},
// 此处不能使用template,因为运行时没有编译器
render(h) {
// <a href="#/about">abc</a>
// <router-link to="/about">
// h(tag, data, children)
return h('a', { attrs: { href: '#' + this.to }}, this.$slots.default)
}
});
Vue.component('router-view', {
render(h) {
// 获取path对应的Component
let component = null;
this.$router.$options.routes.forEach(route => {
if (route.path === this.$router.current) {
component = route.component;
}
})
return h(component)
}
});
}
- 使用路由映射优化
// cvue-router.js
class CVueRouter {
constructor(options) {
// ...
// 创建一个路由映射表
this.routeMap = {};
options.routes.forEach(route => {
this.routeMap[route.path] = route;
});
}
}
// cvue-view.js
export default {
// 因为render中引用了current,所以当current有变化时render函数会被重新调用
render(h) {
// 获取path对应的Component
// let component = null;
// this.$router.$options.routes.forEach(route => {
// if (route.path === this.$router.current) {
// component = route.component;
// }
// })
const { routeMap, current } = this.$router;
const component = routeMap[current].component || null;
return h(component);
}
};
- 处理嵌套路由
// cvue-router.js
class CVueRouter {
constructor(options) {
this.$options = options;
// 需要创建响应式的current属性
// Vue.util.defineReactive(this, 'current', '/');
this.current = window.location.hash.slice(1) || '/';
Vue.util.defineReactive(this, 'matched', []);
// match方法可以递归遍历路由表,获得匹配关系数组
this.match();
// 监控url变化
window.addEventListener('hashchange', this.onHashChange.bind(this));
window.addEventListener('load', this.onHashChange.bind(this));
}
onHashChange() {
console.log(window.location.hash)
this.current = window.location.hash.slice(1);
this.matched = [];
this.match();
}
match(routes) {
routes = routes || this.$options.routes;
// 递归遍历
for (const route of routes) {
if (route.path === '/' && this.current === '/') {
this.matched.push(route)
return
}
if (route.path !== '/' && this.current.indexOf(route.path) != -1) {
this.matched.push(route)
if (route.children) {
this.match(route.children)
}
return
}
}
}
}
// crouter-view.js
export default {
render(h) {
// 标记当前router-view的深度
this.$vnode.data.routerView = true;
let depth = 0;
let parent = this.$parent;
while (parent) {
const vnodeData = parent.$vnode && parent.$vnode.data;
if (vnodeData) {
if (vnodeData.routerView) {
// 如果当前parent是一个router-view
depth++;
}
}
parent = parent.$parent;
}
let component = null;
const route = this.$router.matched[depth];
if (route) {
component = route.component;
}
return h(component);
}
};