前言
在技术的世界,没有奇迹,只有精妙的,令人咂舌的技术运用。 ---- 南方小菜语
看到一句话,前端的革命性事件:ajax实现主动请求局部刷新,路由控制权的掌控;前者很好理解,后者越觉得很让人惊喜,以往自己开发项目的固态思维:
- 后端服务器定义两类接口,页面跳转、数据返回;
- 前端获取数据并渲染页面,关注界面及用户体验;
- 再加上一些数据库加密避免http无状态而利用session等等blablabla的小点。 但前端路由横空出世,顿时天雷地火,把我后端的那点小而傻的沾沾自喜轰炸的渣都不剩,一个新事物的流行,必然在于它解决的一些问题,前端路由指出了传统的三大问题:
- 页面跳转白屏,刷新缓慢
- 在某些场合中,用ajax请求,可以让页面无刷新,页面变了但Url没有变化,用户就不能复制到想要的地址
- 加大服务器的压力,代码冗合。 于是一个全栈项目成为了这样的实现:
- 后端服务器定义数据返回API;
- 前端构建SPA应用,调用接口并基于MVVM进行双向数据绑定渲染
- 前端路由实现无刷新内容更新
一如前端路由深似海,从此再无进度条
当然,前端路由也存在缺陷:使用浏览器的前进,后退键时会重新发送请求,来获取数据,没有合理地利用缓存。但总的来说,现在前端路由已经是实现路由的主要方式了。
思路实现
首先先不聊怎么实现,先思考
需求是什么:前端无刷新更新挂载节点的内容
变化点是什么: location
即点击链接后url发生变化,每个变化对应一个挂载点的内容(很自然的,我们需要一个路由表,即路径与挂载点内容的k-v,可用通过json实现)
找到合理的钩子函数: (现而今主要是hashchange/popstate)
技术实现
传统后端路由每次跳转都刷新页面,另发起一个新的请求,会给用户带来的白屏、耗时等较差体验。因此前端路由采用的是立即加载的方式,不再向服务器请求,而是加载路由对应的组件;而这种思路的实现主要采用两种方案:hashchange 以及 history
- hash
- 基於hashchange事件,通過window.location.hash 获取地址上的hash值
- 通过构造Router类,构造传参配置routes对象设置hash与组件内容的对应
- history
- 借助vue的mvvm,通过vue中的data的current来设置要渲染的router-view,从而达到动态的spa
history
html
<div>
<a href="javascript:;" data-href="/">home</a>
<a href="javascript:;" data-href="/book">book</a>
<a href="javascript:;" data-href="/movie">movie</a>
<div id="content"></div>
</div>
复制代码
js
//路由类
class Router {
constructor(opts) {
//路由表
this.routes = {},
this.init();
this.bindEvent();
opts.forEach(item => {
this.route(item.path, () => {
document.getElementById('content').innerHTML = item.component;
})
})
}
init() {
//页面初始化时渲染路由
window.addEventListener('load', this.updateView.bind(this));
// 当活动历史记录条目更改时, 将触发popstate事件。
// 如果被激活的历史记录条目是通过对history.pushState() 的调用创建的,
// 或者受到对history.replaceState() 的调用的影响,
// popstate事件的state属性包含历史条目的状态对象的副本。
window.addEventListener('popstate', this.updateView.bind(this));
}
// 路由渲染
updateView() {
const currentUrl = window.location.pathname || '/';
this.routes[currentUrl] && this.routes[currentUrl]();
}
// 配置路由
route(path, fn) {
this.routes[path] = fn;
}
push(url){
window.history.pushState({},null,url);
this.updateView()
}
// 为超链接绑定事件
bindEvent() {
const _this = this;
const links = document.getElementsByTagName('a');
[].forEach.call(links, link => {
link.addEventListener('click', function () {
const url = this.getAttribute('data-href');
console.log(url);
_this.push(url);
})
})
}
}
//实例化路由
const router = new Router([{
path: '/',
component: 'home'
}, {
path: '/movie',
component: 'movie'
}, {
path: '/book',
component: 'book'
}])
复制代码
hash
hash也存在下面几个特性:
URL中hash值只是客户端的一种状态,也就是说当向服务器端发出请求时,hash部分不会被发送。 hash值的改变,都会在浏览器的访问历史中增加一个记录。因此我们能通过浏览器的回退、前进按钮控制hash的切换。 我们可以使用hashchange事件来监听hash的变化。
出发hsah变化的方式也有两种,一种是通过a标签,并设置href属性,当用户点击这个标签后,URL就会发生改变,也就会触发hashchange事件了
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<a href="#/" data-href="/">home</a>
<a href="#/book" data-href="/">book</a>
<a href="#/movie" data-href="/">movie</a>
<div id="content">
</div>
<script src="https://cdn.bootcss.com/vue/2.6.10/vue.min.js"></script>
<script>
// window.onload = function (params) {
// window.location.href += '#/'
// }
const Home = {
template: '<div>home</div>'
}
const Book = {
template: '<div>book</div>'
}
const Movie = {
template: '<div>movie</div>'
}
class Router {
constructor(opts) {
// this.path = opts.path;
// this.component = opts.component;
// this.routes = opts.routes;
this.routes = {
}
// console.log(opts);
opts.forEach(item => {
this.route(item.path,()=>{
document.getElementById('content').innerHTML = item.component;
})
})
console.log(this.routes);
this.init()
}
bindEvent() { }
init() {
window.addEventListener('load',this.updateView.bind(this))
window.addEventListener('hashchange', this.updateView.bind(this))
}
updateView(e) {
// console.log(e,'updated');
// console.log(e.newURL.indexOf(e.oldURL));
// console.log(e.newURL.substring(e.newURL.indexOf(e.oldURL)));
const hashTag = window.location.hash.slice(1) || '/'
console.log(window.location.hash.slice(1));
this.routes[hashTag] && this.routes[hashTag]()
}
route(path,cb){
this.routes[path] = cb;
}
}
new Router([
{
path: '/',
component: 'home',
},
{
path: '/book',
component: 'book'
},
{
path: '/movie',
component: 'movie'
}
])
</script>
</body>
</html>
作者:南方小菜
链接:https://juejin.im/post/5d00ea21f265da1b6836b5f2
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。