前后端协作历史 && SPA
Jonnzer Lv4

一种技术登场,总有着它的使命。

前言

今天我们要聊的是路由

路由是什么?路由是浏览器的地址栏的 URL,往往不同路由对应不同业务模块。

比如:www.example.com/photo 指的是photo页面; www.example.com/profile 指的是个人资料页面;

SPA ( single page application ): 单页面应用。作为前端路由能力的解决方案。

回溯

1. 前后端的协作发展

1 前后端没分离:

特点:

(1)项目与页面结构:前端项目依附在后端指定目录里,如 wwwroot。并且页面和结构相对耦合度高,比如JSP、.net。

前端除了编写自己业务,还得了解后台语法,无形提高很多维护成本。

(2)路由能力:前端缺乏路由能力。请求新路由,会跳转到新的网页,并从服务器端下载 HTML,加载样式,JS等等;

重复请求公共页面,会造成资源浪费;

(3)数据获取:后台会在业务结构里传递全局变量,前端需拿到处理逻辑。另一种则是 Ajax 请求;

优点:SEO友好,首屏渲染快;

缺点:维护成本高,资源浪费;

技术栈:JAVA、PHP、.net、Ajax

2 前后端部分分离:

特点:

(1)项目结构:除了前后端不分离,还存在 把部分脱离后端业务的前端页面(定制或纯展示),放在云存储里。

如:https://www.cdnjs.com/custom-h5.html

(2)路由能力:window.History 模块推出了API pushState,让前端可以实现路由改变。

结合 aJax技术,JQuery推出了pJax库; 具体表现为,后台识别为pjax请求,返回页面某个区域的ajax请求html字符串;

前端替换这个区域的dom渲染,同时利用 pushState 改变路由;

技术栈:Ajax、Pjax、CDN

3 前后端分离:

繁杂的前端业务结构和日益庞大的后台数据逻辑,已经很难再合并一起开发;

特点:

(1)前端的工程化:从工具链(Gulp、Grunt)到大型构建(Webpack),项目脚手架。并独自部署维护项目;

(2)路由:前端可以构建大型应用,并以 SPA 模式去维护项目的跳转路由; 提升用户体验,节省下载资源;

三大框架(Vue React Angular)对 SPA 实践。React-router、Vue-router等;

本文的例子也是基于vue-router 展开;

优点:便于维护; 缺点:首屏渲染白屏时间过长; SEO不友好;

技术栈:Vue React Angular 、Nginx、WebPack

4 前端 SSR:解决SEO问题,将web代码放在服务端渲染。涉及篇幅较多,会另开介绍;


2. SPA

那么我们已经了解到 SPA 是一种路由解决方案。

对于 SPA 理解:

单页 single page 的理解就是从开始加载到离开浏览器,前端都是同一个 HTML。

只是在这个页面做路由监听,以及对应处理(跳转路由,切换DOM结构)。

现在的前端框架对虚拟DOM,数据绑定已是驾轻就熟,在监听路由改变时,无刷新切换路由和更新视图。

application 的理解:当页面和业务规模到一定程度的时候,就是一个应用。

对于 SPA 原理:

路由有两种形式:

(1)带 hash: URL中有#,后面跟着的是hash值

路由例子:https://www.google.com/#hash

设置和获取:`location.hash`

监听改变:
1
window.addEventListener('hashchange', () => { // code... })
(2)不带 hash (也称 history模式)

路由例子:https://www.example.com/user

设置和获取:
1
2
3
4
5
// 获取
location.pathName + location.search + location.hash, 处理 query
// 跳转:参数说明:data 设置后是可以在history.state获取, title是暂时用不上的,url是要跳转的路由,字符串形式存在
window.history.pushState(data, title, url)
window.history.replaceState(data, title, url)
监听改变:
1
window.addEventListener('popstate', () => { //code... })

3. Vue-router 构建 SPA 的一些思考

1 install 钩子方法,注册 vue 组件 router-link router-view 。扩展当前 vue 根实例。也巧妙的给 vue 原型赋值: $route 和 $router,并做了防止原型链污染。

1
2
3
4
5
6
7
  // vue.prototype.$router 是 router实例上一些方法
// 如果是new vue()的新实例,this._routerRoot值是没有的,值为undefined。所以无需担心原型链污染
Object.defineProperty(vue.prototype, '$router', {
get () {
return this._routerRoot._router // 只有传入router以及配置的vue实例才会有值
}
})

Vue.util.defineReactive 方法,给指定对象定义响应式。这个方法,实现了在vue实例上收集当前路由的依赖。

定义 router-view 组件时,用了函数式组件的写法:funtional: true。特点是:没有this 没有data 没有生命周期,减少性能消耗。

router-view 中的做法:路由匹配得到的数组 类似 [{ path: ‘/home’, component: aa}, {path: ‘/home/a’}, component: bb] ,

递归改变数组匹配深度,调用render 函数,传入对应组件渲染。保证从子到父都渲染到。有点类似冒泡。

2 history 为核心模块。专门处理底层的路由跳转,路由监听,以及路由匹配。(注:这里的底层,是不会直接暴露给用户使用的)

它考虑的很周全,公用的方法采取继承的方式。公用的方法有 存储和更新当前路由、路由匹配、创建路由方法、队列处理周期钩子(如 beforeEach)

两种路由模式(hash history) 的不同之处:

(1)获取当前路径

(2)监听路由改变:监听的事件不一样,触发的回调也不一样

我们对 $routerd的API 的操作,都会直接映射到这两个基类去操作。比如 push 其实调用的是 history.push 方法,而 push 方法在两模式下实现的方式也各有不同。

队列处理钩子这部分,用到了循环运行迭代器的思想。递归执行到最后,执行回调函数。(点此有介绍)

3 Matcher 模块。负责处理用户 router 配置,递归映射成如: url => route的 map 结构,增加路由等;

4. 总结

结合现在前端框架的组件能力以及浏览器相关 API(location、history、event),也是可以实现 SPA 的。

感谢现在的前端框架,能让我们学的这么多技巧和知识!

  • 本文标题:前后端协作历史 && SPA
  • 本文作者:Jonnzer
  • 创建时间:2022-04-13 19:06:24
  • 本文链接:https://jonnzer.github.io/2022/04/13/技术/前后端协作&&SPA/
  • 版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
 评论