实现router核心
- URL的变化不引起页面刷新
- 检测URL的变化
改变 URL 的方式只有这几种:
- 通过浏览器前进后退改变 URL
- 通过
<a>标签改变 URL - 通过window.location改变URL
原生js实现方法
hash 方式实现
- hash 会在会在路径前加一个 #,改变URL中的hash部分不会引起页面刷新
- 通过 hashchange 事件监听URL的变化
<body>
<a href="#/">home</a>
<a href="#/about">about</a>
<div class="routerview"></div>
<script>
let routerView = [
{ path: '/', component: "<div>home</div>"},
{ path: '/about', component: "<div>about</div>"},
]
window.addEventListener("hashchange", () => {
if (!location.hash) {
location.hash = "/#"
}
RouterRender(location.hash)
})
// 初始化
if (!location.hash) {
location.hash = "#/"
}
RouterRender(location.hash)
function RouterRender(url) {
var resContent
routerView.forEach((item, i) => {
if (url == ('#' + item.path)) {
resContent = item.component
}
let divView = document.querySelector(".routerview")
divView.innerHTML = resContent
})
}
</script>
</body>
history 实现
- pushState 和 replaceState 两个方法,这两个方法改变 URL 的 path 部分不会引起页面刷新
popstate 事件有些不同:
- 通过浏览器前进后退改变 URL 时会触发 popstate 事件
- 通过pushState/replaceState或
<a>标签改变 URL 不会触发 popstate 事件。 - 好在我们可以拦截 pushState/replaceState的调用和
<a>标签的点击事件来检测 URL 变化 - 通过js 调用history的back,go,forward方法课触发该事件
所以监听 URL 变化可以实现,只是没有 hashchange 那么方便。
<body>
<a href="/">home</a>
<a href="/about">about</a>
<div class="routerview"></div>
<script>
let routerView = [
{ path: '/', component: "<div>home</div>"},
{ path: '/about', component: "<div>about</div>"},
]
window.addEventListener("DOMContentLoaded", () => {
// 获取所有的a标签
var linkList = document.querySelectorAll('a[href]')
linkList.forEach(item => {
item.addEventListener('click', (e) => {
// 取消默认点击跳转事件
e.preventDefault()
// 该方法在历史堆栈添加一个状态,并不会发生页面跳转
let href = item.getAttribute('href')
history.pushState(null, "", href)
RouterRender(href)
})
})
})
// 初始化
history.pushState(null, "", "/")
RouterRender("/")
function RouterRender(url) {
var resContent
routerView.forEach((item, i) => {
if (url == item.path) {
resContent = item.component
}
let divView = document.querySelector(".routerview")
divView.innerHTML = resContent
})
}
window.addEventListener("popstate", () => {
RouterRender(location.pathname)
})
</script>
</body>
VueRouter 实现
创建Vue项目,安装Router
在src的router文件夹下的index.js文件可以看到如图;
- 使用了Vue.use,所以Router里面拥有一个install函数
- VueRouter是一个类,需要一些参数。
install函数
- (给每个vue实例添加东西的)—— Vue作为install的第一个参数
- 注意:相同的插件不会重复下载 (插件会放在installedPlugins里面)
因此,我们需要将常量和组件放在install函数中,进行导入加载。
创建小刘Router: XlRouter
- 解析index.js文件中新建实例传的参数
- 建立路由和组件的映射
- 根据路由模式,初始化URL监听事件
- 导入路由组件,添加router对象 Vue.mixin里面的$option 是Vue初始化实例中的参数(main.js文件中)
new Vue({
router,
render: h => h(App)
}).$mount('#app')
import RouterLink from "./RouterLink.vue";
import RouterView from "./RouterView.vue";
class XlRouter {
constructor(options) {
// 判断是否设置了模式
this.mode = options.mode || "hash";
// 传入数组的路由配置
this.routes = options.routes || [];
// / 根据配置创建路由的匹配映射
this.routesMap = this.createMap(this.routes);
// 路由信息
this.history = {
current: null,
};
// 根据模式来匹配路由
this.init();
}
init() {
if (this.mode === "hash") {
// 判断hash是否存在
location.hash || (location.hash = "/");
window.addEventListener("DOMContentLoaded", () => {
this.history.current = location.hash.slice(1);
});
window.addEventListener("hashchange", () => {
this.history.current = location.hash.slice(1);
});
}
if (this.mode === "history") {
window.addEventListener("DOMContentLoaded", () => {
this.history.current = location.pathname
});
window.addEventListener("popstate", () => {
this.history.current = location.pathname
});
}
}
createMap(routes) {
// 遍历route信息对象,建立路径和组件的映射
return routes.reduce((pre, current) => {
pre[current.path] = current.component;
return pre;
}, {});
}
}
// 使用 install方法,会传入Vue对象作为参数
XlRouter.install = function (Vue) {
Vue.mixin({
// 给vue原型链添加 $route $router
beforeCreate() {
// 如果是根组件
if (this.$options && this.$options.router) {
this._root = this;
this._router = this.$options.router;
} else {
// 如果是子组件
this._root = this.$parent && this.$parent._root;
}
Object.defineProperty(this, "$router", {
get() {
return this._root.router;
},
});
Object.defineProperty(this, "$route", {
get() {
return this._root.router.history.current;
},
});
},
});
Vue.component("router-link", RouterLink);
Vue.component("router-view", RouterView);
};
export default XlRouter;
路由组件
Router-Link
- a标签根据路由模式进行不同的URL绑定
- 插槽用于存放写在组件内的内容
<template>
<a :href="mode === 'hash' ? '#' + to : to">
<slot></slot>
</a>
</template>
<script>
export default {
props: {
// route-link to绑定跳转路由信息
to: String
},
data () {
return {
mode: this._self._root._router.mode
}
}
}
</script>
Router-View
- 利用Vue内置组件
component进行组件切换 - 利用data数据双向绑定,路由变化更改history对象,达到更换组件
<template>
<div>
<component :is="component"></component>
</div>
</template>
<script>
export default {
data() {
// 当前路由
let current = this._self._root._router.history.current;
// 路由映射
let routesMap = this._self._root._router.routesMap;
return {
routesMap,
history: this._self._root._router.history,
component: routesMap[current],
};
},
mounted() {
let mode = this._self._root._router.mode;
if (mode === "hash") {
window.addEventListener("hashchange", () => {
this.component = this.routesMap[this.history.current];
});
}
if (mode === "history") {
window.addEventListener("DOMContentLoaded", () => {
this.component = this.routesMap[this.history.current];
});
window.addEventListener("popstate", () => {
this.component = this.routesMap[this.history.current];
});
}
},
};
</script>
<style></style>
使用
可以将XlRouter和两个路由组件放在router文件夹下,进行使用:
修改index.js文件如下
import Vue from 'vue'
// import VueRouter from 'vue-router'
import VueRouter from "./XlRouter"
import Home from '../views/Home.vue'