背景
前后端开发未分离的时候,路由基本上都是由服务端控制。
前端/客户端 发起HTTP请求 -> 服务端 -> url 路由去匹配不同的路由 / 返回不同的数据(Restful接口)
优点: ssr, 直接返回了一个html, 渲染了页面结构, seo效果好,首屏渲染耗时短。
缺点: 前端代码和服务端代码融合在一起,开发协同很乱,服务端压力大,构建html端过程放在服务器上。
单页面应用
SPA单页面应用。
页->html.
单页 -> 只有一个html文件
特点:
1、页面中的交互式不刷新页面的,
2、加载过的公共资源,无重复加载。除了首页之外的其他页面,都是通过异步组件的方式注册。
前端路由router 原理及其表现。
vue -> hash, history
react -> hash, history
1、页面间的交互不会刷新页面。
2、不同 url/路径/路由 会渲染不同的内容。
Hash 和 History的区别?
1、hash 有#, history没有
2、hash的#部分内容不会传给服务端, history的所有url内容服务端都可以获取到。
3、history路由, 应用在部署的时候,需要注意html文件的访问。
4、hash 通过 hashchange监听变化, history通过popstate监听变化。
hash特性
1、url中带有一个#,#只是浏览器/客户端端状态,不会传递给服务端
2、hash值的改变不会导致页面刷新
3、hash值的更改,会在浏览器访问历史中添加一条记录。可以通过浏览器的返回、前进按钮来控制hash的切换。
4、hash值的改变,触发hashchange事件
window.addEventListener('hashchange', ()=>{});
5、如何更改hash
1、location.hash = '#aaa';
2、<a href="#user">点击跳转到user</a>
History 特性
hash有个#符合,不美观,服务端无法接受到hash部分。
window.history.back();
window.history.forward();
window.history.go(-3);
window.history.pushState();
window.history.replaceState();
pushState / replaceState 的参数
window.history.pushState(null, 'new', path);
1、state, 是一个对象,是一个与指定网址相关的对象
2、title, 新页面的地址
3、url, 页面新的地址
面试题
1、pushState会触发popState事件吗
pushState/replaceState 都不会,需要收到触发页面的重新渲染。
2、什么情况下会触发popState事件
1、点击浏览器的后退按钮
2、点击浏览器的前端按钮
3、js back
4、js forward
5、js go
nginx配置
1、index.html存在服务器本地
www.sb.com/a/
www.sb.com/b/
```nginx
try_files $url $rul/ /home/dist/index.html;
```
2、index.html 存在于远程地址。oss/cdn
nginx 配置在a服务器,Index.html被传到了cdn上。
www.sb.com/main/a/
www.sb-cdn.com/file/index.html
```nginx
location /main/{
rewrite^ /file/index.html break;
proxy_pass https://www.sb-cdn.com;
}
```
hash路由实现
// index.html
<body>
<div id="container">
<a href="#white">白色</a>
<a href="#green">绿色</a>
<a href="#gray">灰色</a>
<button onclick="window.history.go(-1)">按钮</button>
<script src="./index.js" ></script>
</div>
</body>
// index.js
class HashRouter{
/** 只是做了一层存储 */
route(path, cb){
this.routes[path] = cb || function() {};
}
/**
* 渲染当前路径对应的操作
*/
refresh(){
const path = `/${location.hash.slice(1) || ''}`
this.routes[path]();
}
constructor(){
this.routes = {};
this.refresh = this.refresh.bind(this);
window.addEventListener('load', this.refresh);
window.addEventListener('hashchange', this.refresh);
}
}
const body = document.querySelector("body");
function changeBgColor(color){
body.style.backgroundColor = color;
}
const Router = new HashRouter();
Router.route('/white', function(){
changeBgColor('white');
})
Router.route('/green', function(){
changeBgColor('green');
})
Router.route('/gray', function(){
changeBgColor('gray');
})
history路由实现
// index.html
<body>
<div class="container">
<a href="/green">绿色</a>
<a href="/gray">灰色</a>
<a href="/">白色</a>
<button onclick="window.history.go(-1)">按钮</button>
<script src="./index.js" ></script>
</div>
</body>
// index.js
class HistoryRouter{
constructor(){
this.routes = {};
this.bindPopState();
this.init(location.pathname);
}
init(path){
window.history.replaceState({path}, null, path);
this.invockPathCallback(path);
}
invockPathCallback(path){
const cb = this.routes[path];
cb && cb();
}
route(path, callback){
this.routes[path] = callback || function(){};
this.invockPathCallback(path);
}
go(path){ console.log(path);
window.history.pushState({ path }, null, path);
this.invockPathCallback(path);
}
bindPopState(){
window.addEventListener('popstate', function(e){
const path = e.target && e.target.path;
this.invockPathCallback(path);
})
}
}
const body = document.querySelector("body");
function changeBgColor(color){
body.style.backgroundColor = color;
}
const Router = new HistoryRouter();
Router.route('/', function(){
changeBgColor('white');
})
Router.route('/green', function(){
changeBgColor('green');
})
Router.route('/gray', function(){
changeBgColor('gray');
})
const container = document.querySelector('.container');
container.addEventListener('click', function(e){
if(e.target.tagName === 'A'){
e.preventDefault();
Router.go(e.target.getAttribute('href'));
}
})
全局导航守卫
1、router.beforeEach(to, from, next)
2、router.beforeResolve()(会在导航被确认之前、所有组件内守卫和异步路由组件被解析之后调用)
3、router.afterEach(to,from)
路由独享守
router.beforeEnter(只在进入路由时触发,不会在 params、query 或 hash 改变时触发)
组件内守卫
export default {
beforeRouteEnter(to, from) {
// 在渲染该组件的对应路由被验证前调用
// 不能获取组件实例 `this` !
// 因为当守卫执行时,组件实例还没被创建!
},
beforeRouteUpdate(to, from) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 `/users/:id`,在 `/users/1` 和 `/users/2` 之间跳转的时候,
// 由于会渲染同样的 `UserDetails` 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 因为在这种情况发生的时候,组件已经挂载好了,导航守卫可以访问组件实例 `this`
}, beforeRouteLeave(to, from) {
// 在导航离开渲染该组件的对应路由时调用
// 与 `beforeRouteUpdate` 一样,它可以访问组件实例 `this`
},
}
导航守卫被触发的过程
- 导航被触发。
- 在失活的组件里调用
beforeRouteLeave守卫。 - 调用全局的
beforeEach守卫。 - 在重用的组件里调用
beforeRouteUpdate守卫(2.2+)。 - 在路由配置里调用
beforeEnter。 - 解析异步路由组件。
- 在被激活的组件里调用
beforeRouteEnter。 - 调用全局的
beforeResolve守卫(2.5+)。 - 导航被确认。
- 调用全局的
afterEach钩子。 - 触发 DOM 更新。
- 调用
beforeRouteEnter守卫中传给next的回调函数,创建好的组件实例会作为回调函数的参数传入。