Vue 学习(九、SPA-单页面应用 与 前端路由)
作者:互联网
文章目录
一、前后端架构
常见的前后端架构有 服务器生成页面、前后端分离、单页面应用三种,它们有着各自的特点,可以根据实际需求选
择不同的前后端架构方式
1. 服务器生成页面
服务器生成页面架构的 请求-响应 流程图:
这是比较早期的架构方式,通过浏览器向服务器发送一个请求,服务器找到与请求匹配的 JSP 文件,然后执行文件
内部的业务逻辑,最终生成一个完整的、包含数据的 HTML 页面返回给浏览器显示
优点:
架构单一,部署简单
缺点:
1. 因为服务器返回的是一个完整的 HTML, 所以往往内容比较多,占用网络也更多,浏览器白屏时间更长
2. 因为返回的是 HTML 结构,所以原生安卓、原生IOS 或 其他前端语言不能通用该接口,不利于跨终端开发
2. 前后端分离
随着移动设备的普及,多终端开发也越来越普遍,显然 <服务器生成页面> 这种单纯返回 HTML 结构的架构方式已
经不能满足实际产品需求,就是在这种背景下产生了前后端分离的概念,其宗旨是,服务器不再返回完整的 HTML
结构,而是返回 UI 中要用的数据,然后由前端技术拿着数据自己渲染画面
前后端分离概念图:
这种架构可以提升接口的复用性,它不在乎前端使用的具体技术,它只需要提供该功能需要的数据
前后端分离架构的 请求-响应 流程图:
前后端分离的架构解决了跨终端时接口复用性的问题,同时也改变了我们 HTML 页面的 请求-响应 流程,浏览器
先向静态资源服务器发起请求,拉取未填充数据的 HTML 页面,在 HTML 中再利用 AJAX 向接口服务器发送请求,
获取需要的数据,最后再通过 HTML 中的 JS 将数据填充形成一个完整的 HTML 页面
缺点:
网络请求会多一些,每次画面跳转既要访问静态资源服务器,又要访问接口服务器
优点:
1. 可以增加后端接口的跨终端复用性,
2. 开发人员职责分明,后端开发人员只关注接口代码,UI 布局和渲染相关代码由前端开发人员负责
3. 单页面应用 - SPA
单页面应用 ( Single Page Application ) 算是前后端分离的改良版,整个应用程序只有一个 HTML 页面,每次打开应
用时,浏览器会向静态资源服务器请求该页面,后续的页面跳转都是由该 HTML 中引入的 JS 动态渲染,不需要再向
资源服务器发起请求
单页面应用 请求-响应 流程图:
优点:
1. 页面跳转不需要发送网络请求,而是由 JS 渲染,这样页面转场的过度效果更容易实现
2. 跳转不需要发送网络请求,所以 UI 响应更快速、用户体验更好
缺点:
1. 页面都是 JS 动态渲染的,非单独的静态 HTML ,所以不利于 SEO 优化
二、路由
1. 服务器端路由
路由是服务器端开发常提的概念,它包含一个路由关系表,其中保存着路径和处理程序的对应关系,当浏览器发起
一个请求到达服务器时,我们需要通过路由表找到具体的处理程序,如下图的 3、4、5 就是一个路由的大概流程:
2. 前端路由
和服务器端一样,前端路由也是根据 URL 地址来匹配画面,使用 JS 渲染出不同地址对应的画面,但是当页面跳转
事件发生时,我们如何修改 URL 又成了一个新问题, 如果直接使用 window.location.href
的方式来修改 URL 路径,
浏览器会发生默认跳转,向 URL 所指的服务器发送请求,这显然不符合单页面应用的思想
使用 window.location.href
的方式跳转页面:
上图中,最初 Network
里的信息是空的,当在 Console
中使用 window.location.href
修改地址后,虽然地址栏中的
URL 发生了变化,但是再看 Network
中已经有了向服务器请求的信息,所以这种方式并不适合单页面应用
既然 window.location.href
的方式不适合单页面应用,那么就得考虑其他方式,在这里介绍两个可以改变 URL 又不会
发生服务器请求的方法
1) 使用 hash 的方式
其语法非常简单,window.location.hash = 'URL 地址'
,先用一下看看效果
和前面一样的操作,但使用 window.location.hash
修改地址后,Network
中却没有向服务器发起请求的信息
(favicon.ico 可以忽略) ,这正满足了 URL 改变而不发生请求的要求,细心的可以发现,地址栏中的 URL 里多
了个 #
,这是 hash
方式的特点,#
就像一个分界线,当 #
后的内容改变时, 不会向服务器发送请求
现在用 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">
<div>首页</div>
<button onclick="btnClick('product')">商品</button>
<button onclick="btnClick('detail')">详情</button>
</div>
<script>
// 按钮点击事件
function btnClick(path) {
window.location.hash = path
}
// 监听 hash 变化
window.onhashchange = (e) => {
// 清除原内容
const container = document.querySelector('#container')
container.remove()
// 显示新内容
const path = window.location.hash.slice(1)
// 商品页内容
if ('product' === path) {
const span = document.createElement('span')
span.innerText = '商品页'
document.body.appendChild(span)
}
// 详细页内容
if ('detail' === path) {
const span = document.createElement('span')
span.innerText = '详情页'
document.body.appendChild(span)
}
}
</script>
</body>
</html>
运行效果:
2) 使用 history 的方式
HTML 5 推出的 history 相关 Api,也可以做到改变 URL 而不发送请求的功能,在此简单介绍两个 Api 的用法
api | 参数 | 描述 |
---|---|---|
window.history.pushState({}, ‘’, ‘路径’) | 前两个参数不常用,第三个参数代表修改的路径 | 路径会保存在历史记录中,支持前进后退按钮 |
window.history.replaceState({}, ‘’, ‘路径’) | 前两个参数不常用,第三个参数代表修改的路径 | 路径不会保存在历史记录中,不支持前进后退按钮 |
用 history 的方式,来简单实现一个前端路由
前面用 hash 的方式写前端路由时,是将创建的 html 文件直接用浏览器打开,这种情况浏览器默认使用 file 协议,
而history.pushState
和 history.replaceState
使用 file 协议会涉及到跨域的问题,我们应该将页面放在服务器,
然后让浏览器用 http 协议去访问服务器中的页面,这样才能避免跨域问题
搭建服务器的方式有很多,此处我使用一种对我来说比较方便的方式,就是用 vue-cli 创建一个项目,然后使用
其配置的 webpack-dev-server
服务器,相关知识 和 vue-cli 的使用方式都已经介绍过,就不再记录过程
通过 Vue-CLI 创建好 vue 项目后,直接修改 app.vue
文件:
<template>
<div id="app">
<div>首页</div>
<button @click="btnClick">商品</button>
</div>
</template>
<script>
// 自定义事件 - 监听 pushState 事件
const bindEventListener = function (type) {
const historyEvent = history[type]
return function () {
const newEvent = historyEvent.apply(this, arguments)
const e = new Event(type)
e.arguments = arguments
window.dispatchEvent(e)
return newEvent
}
}
history.pushState = bindEventListener('pushState')
// 监听 pushState 事件
window.addEventListener('pushState', function (e) {
// 删除原页面内容
const app = document.querySelector('#app')
app.removeChild(document.querySelector('#app > div'))
app.removeChild(document.querySelector('#app > button'))
// 新建商品页内容
if (e.arguments[2].slice(1) === 'product') {
const span = document.createElement('span')
span.innerText = '商品'
document.body.appendChild(span)
}
})
export default {
name: 'App',
methods: {
btnClick() {
window.history.pushState({}, '', '/product')
}
}
}
</script>
运行效果:
通过 Network
页可以看出,history 的方式也没有发生服务器请求,而且地址栏路径也更清爽,不再有 #
标志,不
过代码中有一点要注意,因为 DOM 默认没有对 history.pushState
事件的监听,所以我们需要自定义一个事件,既
这一部分代码:
const bindEventListener = function (type) {
const historyEvent = history[type]
return function () {
const newEvent = historyEvent.apply(this, arguments)
const e = new Event(type)
e.arguments = arguments
window.dispatchEvent(e)
return newEvent
}
}
history.pushState = bindEventListener('pushState')
3、结语
不论是使用 hash 的方式,还是使用 history 的方式,我们的页面都是由 JS 渲染出来的,而不是通过服务器响应回
来的,这样每个画面就没有缓存,这会导致在使用浏览器的前进和后退按钮时,有可能出现地址变化但是页面不变的
问题,所以要想实现一个可靠性高的前端路由功能,是有很多细节要考虑的
和以前一样,这篇文章浅显的介绍前端路由相关知识,就是为了给后面要学习的路由插件 vue-router 做铺
垫,如果想自定义一个产品级的前端路由组件,那还需要很多知识要去自己摸索和掌握
标签:Vue,const,window,服务器,SPA,页面,路由,history 来源: https://blog.csdn.net/Ares5kong/article/details/122477545