首先我们来谈谈前端路由和后端路由究竟有什么不同呢?
请耐心读完这部分,非常有利于各位同学的理解
- 后端路由:
通过用户输入的url,服务端将其解析并将对应的文件传回前端,前端得以根据这些文件渲染页面,完成后端路由的流程闭环.
2. 前端路由:
前端路由是指智能分析url的变化,并根据预先设置,为匹配到不同的url将前端页面指定区域的渲染改变,整个过程可以不知会服务端,常见于各种SPA的开发过程(也就是非常常用)
而前端路由根据实现方式的不同,又可以分为 hash路由 和 html5history路由.
下面,我们也分别介绍一下这两种前端路由的原理:
(耐心看完,不要觉得难,其实很好理解)
1.hash路由:
类似于http://www.shang.com/#/login
这类路由有个很重要的特点,就是 # 号,所有需要侦测变化的位置都必须在 # 之后, 原因是正常情况下,前端url发生变化,都会刷新页面,但是如果变化出现在 # 以后,则不会出现这种情况.
这也能顺利的达成我们的需求: 在刷新页面的前提下,根据url变化,更改前端的展示内容.
那么问题来了,我们要如何侦测到hash路由的变化呢?
这里有个很好的方案,就是通过监听hashchange这个事件来获取变化,并在事件体中编写我们需要的逻辑.
window.addEventListener('hashchange', MatchAndUpdate);
function MatchAndUpdate() {
// todo...
}
2.html5history路由:
随着2014年,html5规范推出之后,在路由方面喜提三个api,分别是pushState, popState, replaceState.
通过这三个api的使用,我们就可以在不添加 # 的前提下,完成同样的功能---url变化时,不刷新页面,智能变更指定区域的前端渲染.
那么,基础知识我们已经科普完了,后面就是....
本文的重头戏: 徒手编写vue-router
首先我们来看一下,vue的原生路由是什么感觉的?
import VueRouter from 'vue-router';
Vue.use(VueRouter); // 看起来有点中间件的感觉,对吧?
const router = new VueRouter({ // 这一步是用VueRouter包装一下我们的路由数据
mode: 'history', // 模式: hash或者history
routes: [...] // 路由配置
});
new Vue({
router // 这一步,大家都懂得,挂载路由~
});
做完了这些操作,我们就可以使用<vue-router> <vue-view><vue-link>这些常用高阶组件,
其次我们还可以在嵌套子组件中使用诸如this.$router或者this.$route等api.
随后,我们使用vue数据驱动模型来实现类似vue-router的代码.
vue数据驱动模型:
首先我们写一个便于模型监听的route对象.
const currentRoute = {
path: '/login',
query: {}, // 形如 ?query1=''&query2=''这种感觉.
params: {}, // 形如 /:id这种感觉.
name: '' // 路由名
fullPath: '/login', // 完整路径
route: {} // 记录当前路有属性
}
由于route属性里面储存着关于该路由的所有信息,所以我们只需要监听currentRoute.route这个对象即可达成我们需要的效果了.
那么~~~,老规矩,先编写vue的监听器
class Obeserver {
constructor(value) {
this.observe(value);
}
observe(data) {
if(!data && typeof data !== 'object' ) {
return;
}
Object.keys(data).forEach(key=> {
if(typeof data[key] === 'object') {
this.observe(data[key]);
}
defineReactive(data, key, obj[key]);
})
}
}
然后就是编写我们的劫持函数defineReactive;
function defineReactive(data, key, value) {
let dep = new Dep();
Object.defineProperty(data, key, {
get: function() {
if(Dep.target) {
dep.addsub(Dep.target);
}
return value;
},
set: function(newValue) {
if(value !== newValue) {
value = newValue;
dep.notify(); // 通知视图
}
}
})
}
随后,就需要编写我们的容器类(Dep)了,容器类里面包裹着订阅了数据的Watcher类.
class Dep {
constructor() {
this.subs = []; // 用于包裹订阅器的容器数组
}
addsub(sub) {
this.subs.push(sub);
}
notify() {
this.subs.forEach(sub => {
sub.update();
})
}
}
Dep.target = null;
最后,我们需要编写我们的订阅器,也就是Watcher类:
class Watcher {
constructor(vm, props, callback) {
this.vm = vm;
this.props = props;
this.callback = [];
this.callback.push(callback);
this.value = this.getValue();
}
getValue() {
Dep.target = this;
const value = this.vm.data[this.props];
Dep.target = null;
return value;
}
update() {
this.callback.forEach(callbackfn => callbackfn());
}
}
下一步就是,用我们编写好的监听-订阅-视图更新器来控制我们的route.
new Observer(this.current);
new Wathcer(this.current, 'route', this.render.bind(this));
而我们传入的render函数就是负责视图更新的回调函数:
render() {
let i;
if((i = this.current) && (i = i.route) && (i = i.component)){
document.querySelector(`#${this.container}`).innerHTML = i;
}
}
同学们,编写到这里,一个建议的vue-router已经完成了,其实vue-router和vue双向数据绑定原理是非常类似的,主要的地方在于熟悉Object.defineProperty,以及 监听数据 - 储存容器 - 订阅数据 - 视图更新 这个 vue 数据模型.
本次编写的只是个简略版vue-router,但是对于理解原理足矣,后面时间充足的时候,我会将全部逻辑补充完整,各位同学们, 敬请期待吧