什么是路由
- 路由的概念是伴随着SPA 出现的,在此之前,页面的跳转是通过服务器控制的
- 传统的页面跳转是通过前端向后台发送请求;
- 后台通过模版引擎渲染,将一个新的html界面再返回给前台;
- 比如页面跳转时:
- a 标签的 href
- form 表单
- location.href
- 在 SPA 出现之后,前端可以自由地控制组件的渲染,来模拟页面的跳转
- hash路由和history路由
总结:
- 传统的路由是根据url访问相关的controller,进行数据资源和模板引擎的拼接,返回给前端;
- 前端路由使用js根据url返回对应的组件加载;
- 告诉服务端不要处理路由
- 自己对url进行处理
- 根据url的规则匹配加载组件
hash 路由和history 路由的区别
- hash路由一般会带一个#号,不够美观;history 路由不存在这个问题
- 默认hash路由是不会向浏览器发起请求(最早用于做锚点);history路由中的go / back / forward以及浏览器中的前进后退按钮,一般都会向服务器发起请求;(pushState / replaceState 不会)
- history 路由在部署的时候,需要设置只渲染首页
#单个服务器的部署
location / {
try_files uri $uri /xx/xxxx/xxx/index.html
}
#存在代理的情况
locatoin / {
rewrite ^ /file/xx/xxxx/index.html break;
proxy_pass https://www.xxx.cdn.com;
}
- 基于此hash路由是不支持SSR的;但是history 路由是可以的;
- hash 路由的监听一般是用onHashChange 事件,history 路由的监听onPopState进行监听
histroy 路由
history 本质上是一个BOM API,里面有go / forward / back 三个API,以及pushState / replaceState;
- pushState / replaceState 都不会触发popState事件;
- popState 什么时候触发?
- 点击浏览器前进后退按钮;
- go / forward / back;
实现简单的router
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="container">
<a href="./">首页</a>
<a href="./about">关于我们</a>
<a href="./user">用户列表</a>
</div>
<div id="context"></div>
<script>
class BaseRouter {
constructor() {
this.routes = {};
this._bindPopState();
};
route(path, cb) {
this.routes[path] = cb || function () { }
};
go(path) {
window.history.pushState({ path }, null, path);
const cb = this.routes[path];
if (cb) {
cb();
}
}
init(path) {
window.history.replaceState({ path }, null, path);
const cb = this.routes[path];
if (cb) cb();
}
//保证onPopState 在触发的时候页面也会进行更新
_bindPopState() {
window.addEventListener('popState', event => {
const path = event.state && event.state.path;
this.routes[path] && this.route[path]();
})
}
}
const Route = new BaseRouter();
Route.route('./about', () => changeText('关于我们页面'))
Route.route('./user', () => changeText('用户列表页'))
Route.route('./', () => changeText('首页页面'))
function changeText(arg) {
document.getElementById('context').innerHTML = arg;
}
document.getElementById('container').addEventListener('click', e => {
if (e.target.tagName === 'A') {
e.preventDefault();
Route.go(e.target.getAttribute('href'));
}
})
</script>
</body>
</html>
hash
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="container">
<a href="#">首页</a>
<a href="#about">关于我们</a>
<a href="#user">用户列表</a>
</div>
<div id="context"></div>
<script>
class BaseRouter {
constructor() {
this.routes = {};
// this._bindPopState();
// this.init();
this.refresh = this.refresh.bind(this);
window.addEventListener('load', this.refresh)
window.addEventListener('hashchange',this.refresh);
};
//收集路由
route(path, cb) {
this.routes[path] = cb || function () { }
};
//访问路由,执行函数
go(path) {
window.history.pushState({ path }, null, path);
const cb = this.routes[path];
if (cb) {
cb();
}
}
refresh(){
const path = `/${window.location.hash.slice(1) || ''}`
this.routes[path]();
}
init(path) {
window.history.replaceState({ path }, null, path);
const cb = this.routes[path];
if (cb) cb();
}
//保证onPopState 在触发的时候页面也会进行更新
_bindPopState() {
window.addEventListener('popState', event => {
const path = event.state && event.state.path;
this.routes[path] && this.route[path]();
})
}
}
const Route = new BaseRouter();
Route.route('/about', () => changeText('关于我们页面'))
Route.route('/user', () => changeText('用户列表页'))
Route.route('/', () => changeText('首页页面'))
function changeText(arg) {
document.getElementById('context').innerHTML = arg;
}
// document.getElementById('container').addEventListener('click', e => {
// if (e.target.tagName === 'A') {
// e.preventDefault();
// Route.go(e.target.getAttribute('href'));
// }
// })
</script>
</body>
</html>
路由守卫的触发流程
- [组件] - 前一个组件
beforeRouterLeave, - [全局] -
router.beforeEach, - [组件] - 如果是路由的参数变化,触发
beforeRouteUpdate, - [配置文件]里 - 下一个
beforeEnter, - [组件] - 内部声明的
beforeRouteEnter, - [全局] - 调用
beforeResolve, - [全局] -
router.afterEach,
核心原理,promise的链式调用。