实现一个简易的 vue router
1.vue-router 有2种模式 hash 与 history 模式,hash模式就是利用锚点的原理实现跳转(通过修改window.location.hash 值),而history 通过修改浏览器访问历史栈来实现(该模式需要后端支持)。
2.由于History 需要后端支持,这里就不做实现,这里主要实现一下hash 模式路由。
// 这里先引入 vue 库的 cdn
<script crossorigin="anonymous" integrity="sha384-+jvb+jCJ37FkNjPyYLI3KJzQeD8pPFXUra3B/QJFqQ3txYrUPIP1eOfxK4h3cKZP" src="https://lib.baomitu.com/vue/2.6.11/vue.js"></script>
接下来我们先写几个全局组件用于验证使用
let homePage = Vue.component('home-page',{
data:()=>({
title:'this is home page!',
message:'welcome go to home page!'
}),
methods:{
goToNews(){
router.replace({path:'/news',query:{name:'I am home page',action:'I am to go news page'}});
},
goToStore(){
router.replace('/store');
}
},
template:`
<div>
<h2>{{title}}</h2>
<p>{{message}}</p>
<button @click="goToNews">click me go to news page</button>
<button @click="goToStore">click me go to store page</button>
</div>
`
});
let newsPage = Vue.component('news-page',{
data:()=>({
title:'this is news page!',
message:'welcome go to news page'
}),
methods:{
goToHome(){
router.push({path:'/',query:{name:'I am news page',action:'I am to go home page'}});
}
},
template:`
<div>
<h2>{{title}}</h2>
<p>{{message}}</p>
<button @click="goToHome">click me go to home page</button>
</div>
`
});
let storePage = Vue.component('store-page',{
data:()=>({
title:'this is store page!',
message:'welcome go to store page'
}),
methods:{
goToNews(){
router.push('/news');
}
},
template:`
<div>
<h2>{{title}}</h2>
<p>{{message}}</p>
<button @click="goToNews">click me go to news page</button>
</div>
`
});
// 路由数组
const routes = [
{
path:'/',
component:homePage
},
{
path:'/news',
component:newsPage
},
{
path:'/store',
component:storePage
}
]
class Router{
constructor(options){
// 用于存储路径与组件之间当映射关系的仓库
this.routeMap = {};
// 创建一个vue 实例对象 用于 当路由改变时 触发 试图更新
this.app = new Vue({
data:{
currentPath:'/'
}
});
//初始化 路由配置
this.init(options);
//初始化 router-link router-view 组件
this.initComponent();
}
init(options){
// 监听 hashchange 事件 来监控 hash 值改变
window.addEventListener('hashchange',this.onHashChange.bind(this));
// 为每个路由 建立 映射关系
options.routes.forEach(route => {
this.routeMap[route.path] = route.component;
});
//当浏览器 刷新时需要重置 路由
window.onload=function(){
window.location.hash = '/'
}
}
// 当hash 值改变时触发的回调函数
onHashChange(){
let path = this.getHash();
// 判断 hash 值中是否有? (当路由进行query传参来就会出现?)
if(path.includes('?')){
let index = path.indexOf('?');
// 只需要hash 值 ? 之前当字符串,因为我们注册当路由中并没有写?而是纯粹当路径
this.app.currentPath = path.slice(0,index);
}else{
this.app.currentPath = path;
}
}
getHash(){
return window.location.hash.slice(1) || '/';
}
setHash(type,url){
if(type === 'push') window.location.hash = url;
else if(type === 'replace'){
if(window.location.hash.includes('#')){
let index = window.location.hash.indexOf('#')+1;
url = window.location.hash.slice(0,index) +url;
}else{
url = window.location.hash + url;
}
window.location.replace(url);
}
}
initComponent(){
let _this = this;
Vue.component('router-link',{
props:{
to:[String,Object]
},
template:`
<a :href="to">
<slot></slot>
</a>
`
});
Vue.component('router-view',{
render(h){
let component = _this.routeMap[_this.app.currentPath];
return h(component);
}
});
}
push(){
this.setLocation('push',arguments[0]);
}
replace(){
this.setLocation('replace',arguments[0]);
}
setLocation(type,arg){
if(typeof arg === 'string'){
this.setHash(type,arg);
}else if(Object.prototype.toString.call(arg) === "[object Object]"){
let url = arg.path;
// 当传来参数时 对 url 进行拼接
if(arg.query){
url += '?';
for(let attr in arg.query){
url += `${attr}=${arg.query[attr]}&`;
}
// 去除最后多余当一个 & 符号
let index = url.length -1;
url = url.slice(0,index);
}
this.setHash(type,url);
}
}
}
const router = new Router({routes});
new Vue({
el:'#app',
router
})
html 部分
<div id="app">
<div>
<router-link to="#/">首页</router-link>
<router-link to="#/news">新闻</router-link>
<router-link to="#/store">商店</router-link>
</div>
<router-view></router-view>
</div>
效果图: