先简单说一说,我们前端路由的实现主要是为了SPA应用的框架,框架开发都是单页应用,单页应用的特点就是跳转页面的时候的不刷新浏览器,那想要实现跳转页面不刷新浏览器的方式有两种:一种是通过hash的方式、另一种就是通过H5的history的api实现
路由实现的核心原理
- hash的实现原理
<a href="#/home">首页</a>
<a href="#/about">关于</a>
<div id="html"></div>
<script>
window.addEventListener('load',()=>{
html.innerHTML = location.hash.slice(1);
});
window.addEventListener('hashchange',()=>{
html.innerHTML = location.hash.slice(1)
})
</script>
复制代码
- history api的实现原理
<a onclick="go(/home)">首页</a>
<a onclick="go(/about)">关于</a>
<div id="html"></div>
<script >
function go(pathname) {
history.pushState({},null,pathname)
html.innerHTML = pathname;
}
window.addEventListener('popstate',()=>{
go(location.pathname);
})
</script>
复制代码
源码实现
- $options下的router对象是我们在实例化Vue的时候挂载的那个vue-router实例;
- _route是一个响应式的路由route对象,这个对象会存储我们路由信息,它是通过Vue提供的Vue.util.defineReactive来实现响应式的,下面的get和set便是对它进行的数据劫持;
- _router存储的就是我们从$options中拿到的vue-router对象;
- _root指向我们的Vue根节点;
- route和$router是定义在Vue.prototype上的两个getter。前者指向_root下的_route,后者指向_root下的_router
class HistoryRoute{
constructor(){
this.current = null;
}
}
class vueRouter {
constructor(options){
this.mode = options.mode || "hash";
this.routes = options.routes || [];
// 传递的路由表是数组 需要装换成{'/home':Home,'/about',About}格式
this.routesMap = this.createMap(this.routes);
// 路由中需要存放当前的路径 需要状态
this.history = new HistoryRoute;
this.init();//开始初始化操作
}
init(){
if(this.mode == 'hash'){
// 先判断用户打开时有没有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;
})
}
}
createMap(routes){
return routes.reduce((memo,current)=>{
memo[current.path] = current.component
return memo
},{})
}
}
//使用vue.use就会调用install方法
vueRouter.install = function(Vue,opts) {
//每个组件都有 this.$router / this.$route 所以要mixin一下
Vue.mixin({
beforeCreate(){ //混合方法
if(this.$options && this.$options.router){//定位跟组件
this._root = this;//把当前实例挂载在_root上
this._router = this.$options.router // 把router实例挂载在_router上
//history中的current变化也会触发
Vue.util.defineReactive(this,'xxx',this._router.history);
}else {
// vue组件的渲染顺序 父 -> 子 -> 孙子
this._root = this.$parent._root;//获取唯一的路由实例
}
Object.defineProperty(this,'$router',{//Router的实例
get(){
return this._root._router;
}
});
Object.defineProperty(this,'$route',{
get(){
return {
//当前路由所在的状态
current:this._root._router.history.current
}
}
})
}
});
// 全局注册 router的两个组件
Vue.component('router-link',{
props:{
to:String,
tag:String
},
methods:{
handleClick(){跳转方法
}
},
render(h){
let mode = this._self._root._router.mode;
let tag = this.tag;
return <tag on-click={this.handleClick} href={mode === 'hash'?`#${this.to}`:this.to}>{this.$slots.default}</tag>
}
})
Vue.component('router-view',{//根据当前的状态 current 对应相应的路由
render(h){
//将current变成动态的 current变化应该会影响视图刷新
//vue实现双向绑定 重写Object.defineProperty
let current = this._self._root._router.history.current;
let routeMap = this._self._root._router.routesMap
return h(routeMap[current])
}
})
}
export default VueRouter;
复制代码