spa来源
传统的web, 浏览器输入一个url然后返回一个页面.后来, 人们发现这样太浪费资源啦, 比如, 我可能只是一个很小的操作,给空间好友点赞啊之类的 这个时候刷新页面肯定是不行的啊, 那就'局部刷新'吧,就是后来的ajax的应用.
再后来, 机智的开发者又想到, hash路径段(形如#header)是不会像服务器发请求的, 那么我们可以在点击一个锚点的时候加载一个小页面, 局部刷新, 再加上可以操作浏览器的历史记录来实现回退等功能. 那干脆就搞个前端路由吧. 于是前端路由就出现啦.
其实前端路由本质上也是一种局部刷新, 但是更规范. 这次刷新, 拉回来的是一个完整的组件, 这个组件包含了视图和数据. 这里的数据又分为两种, 一种是后端传过来的, 暂且成为数据库数据, 另一种是为了更好地管理视图而在前端生成的数据, 暂且成为视图数据.一通乱说过后, 我们就可以好好分析一下SPA到底是个什么东西啦.SPA是single page application的简称, 中文叫单页应用. 何为单页? 就是只有一个页面, 这个页面一般是index.html, 这是项目的入口文件. 自项目启动到结束, 浏览器中始终都是这个页面, 你看到的变化只是组件之间的创建和删除,就是当路由切换的时候,HTML文件依然是那个文件,只是每次往某个坑比如id=app的div里填的东西不一样.
传统:前端由多个页面组成,后端负责组织数据、实现路由、甚至生成页面内容;前端后端其实是混杂一起的;
当前:前端由单个页面或者少量页面构成(Single Page Application),前端框架负责组织路由,切换页面内容(一般将页面拆分成组件/Componet),后端只负责提供 API 服务并收发数据;
这样前后端就基本分离解耦了。
一.hash路由
我们知道a标签一般拿来跳转路由的,但当a标签加上#时,是不会发生跳转的, 同时触发个window.onhashchange事件或者用window.addEventListener('hashchange', handleHashChange);来监听,当监听到之后对html内容修改
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>前端hash路由</title>
<style type="text/css">
* {
box-sizing: border-box;
}
</style>
<script type="text/javascript">
var contentNode = null;
function onLoad() {
contentNode = document.querySelector("#content");
handleHashChange() // 页面加载时不一定触发hashchange事件,这里手动触发一次
}
window.addEventListener('DOMContentLoaded', onLoad)
// hash路由变化时,根据路由渲染对应 UI
function handleHashChange (e) {
console.log('handleHashChange', window.location.hash);
contentNode.innerHTML= window.location.hash.slice(1)
}
// 监听路由变化
window.addEventListener('hashchange', handleHashChange);
</script>
</head>
<body>
<section>
<aside>
<ul>
<li><a href='#index'>首页</a></li>
<li><a href='#about'>关于</a></li>
</ul>
</aside>
<section>
<main>
<div id="content">
</div>
</main>
</section>
</section>
</body>
</html>复制代码
1.当我们点击a标签(并且hash变化)或者通过浏览器回退(或通过js调用history.back()或者history.go(-1)),前进(或者history.forward()或者history.go(1)),都会触发hashchange事件
2.刷新或第一次进入是不会触发的,所以我们在页面onload之后,手动调用了一次
二.通过history API实现
popstate的说明
1.只有在做出浏览器动作时,才会触发
popstate
popstate
事件,如用户点击浏览器的回退按钮(或者在Javascript代码中调用history.back()),
前进(或者history.forward()或者history.go(1))
2.刷新或第一次进入不一定会触发popstate事件(不同的浏览器在加载页面时处理popstate事件的形式存在差异。页面加载时Chrome和Safari通常会触发(emit )popstate事件,但Firefox则不会。),所以我们可能需要手动调用一次
3.对于a标签,我们写的是正常路由,页面会跳转,必须阻止a标签默认行为,然后我们需要通过 history.pushState或者 history.replaceState修改title,url,并且存储历史记录
注意history.pushState或者 history.replaceState不会触发popstate事件,所以需要手动去修改html的内容
4.history.pushstate有安全限制,其中之一是不允许应用推动跨url-s历史起源;服务器ip的端口不同和直接在电脑本地运行的html都会报错,如下图:
为了测试该功能,我用webpack+webpack-dev-server 简单搭了个本地开发服务
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>test</title>
<script type="text/javascript">
var contentNode = null;
function onLoad() {
contentNode = document.querySelector("#content");
handlePopState();
var linkList = document.querySelectorAll('a[href]')
function handleClickLink (e) {
e.preventDefault(); // 拦截 <a> 标签点击事件默认行为
console.log(e.target.href)
history.pushState(null, '', this.getAttribute('href')) // 点击时使用 pushState 修改 URL和title,并保存路由到历史记录
handlePopState(e) // 并更新手动 UI,从而实现点击链接更新 URL 和 UI 的效果。
}
linkList.forEach(el => el.addEventListener('click', handleClickLink))
}
window.addEventListener('DOMContentLoaded', onLoad)
// 路由变化时,根据路由渲染对应 UI
function handlePopState (e) {
console.log('handlePopState', window.location.pathname);
switch (location.pathname) {
case '/index':
contentNode.innerHTML = 'Index'
return
case '/about':
contentNode.innerHTML = 'About'
return
default:
return
}
}
// 监听路由变化
window.addEventListener('popstate', handlePopState);
</script>
</head>
<body>
<div id="app"></div>
<section>
<aside>
<ul>
<li><a href='/index'>首页</a></li>
<li><a href='/about'>关于</a></li>
</ul>
</aside>
<section>
<main>
<div id="content">
</div>
</main>
</section>
</section>
</body>
</html>
复制代码