SPA

什么是单页面应用?

单页Web应用(single page web application,SPA),就是只有一张Web页面的应用,是加载单个HTML 页面并在用户与应用程序交互时动态更新该页面的Web应用程序,是指在浏览器中运行的应用,它们在使用期间不会重新加载页面。像所有的应用一样,它旨在帮助用户完成任务,比如“编写文档”或者“管理Web服务器”。可以认为单页应用是一种从Web服务器加载的富客户端。


速度:更好的用户体验,让用户在web app感受native app的速度和流畅,
MVC:经典MVC开发模式,前后端各负其责。
ajax:重前端,业务逻辑全部在本地操作,数据都需要通过AJAX同步、提交。
路由:在URL中采用#号来作为当前视图的地址,改变#号后的参数,页面并不会重载。

      (可以参考:http://blog.csdn.net/limm33/article/details/50947212?locationNum=1)


单页Web应用(single page web application,SPA)是当今网站开发技术的弄潮儿,很多传统网站都在或者已经转型为单页Web应用,新的单页Web应用网站(包括移动平台上的)也如雨后春笋般涌现在人们的面前,如Gmail、Evernote、Trello等。如果你是一名Web开发人员,却还没开发过或者甚至是没有听说过单页应用,那你已经Out很久了。


单页Web应用和前端工程师们息息相关,因为主要的变革发生在浏览器端,用到的技术其实还是HTML+CSS+JavaScript,所有的浏览器都原生支持,当然有的浏览器因为具备一些高级特性,从而使得单页Web应用的用户体验更上一层楼。关于单页应用的优点和缺点,网上讲解的文章有很多,这里就不展开论述了。 单页Web应用,顾名思义,就是只有一张Web页面的应用。浏览器一开始会加载必需的HTML、CSS和JavaScript,之后所有的操作都在这张页面上完成,这一切都由JavaScript来控制。因此,单页Web应用会包含大量的JavaScript代码,复杂度可想而知,模块化开发和设计的重要性不言而喻。


以下来自:https://pando.com/2012/12/06/html-javascript-and-the-app-ification-of-the-web/

单页Web应用程序的优点:

  1. 首先,最大的好处是用户体验,对于内容的改动不需要加载整个页面。这样做好处颇多,因为数据层和UI的分离,可以重新编写一个原生的移动设备应用程序而不用(对原有数据服务部分)大动干戈。
  2. 单页面Web应用层程序最根本的优点是高效。它对服务器压力很小,消耗更少的带宽,能够与面向服务的架构更好地结合。



单页Web应用程序的缺点:

虽然还有一些历史遗留问题(大部分是针对HTML5的改进)以及SEO。如果你看中SEO,那就不应该在页面上使用JavaScript,你应该使用网站而不是Web应用。目前该技术还存在一些争议,但这并不是重点,因为这种类型的体系架构为SAAS Web Apps提供了一个极大的可用性。

单页Web应用程序的结构很简单:首先传递HTML文档框架;然后使用JavaScript修改页面;紧接着再从服务器传递更多数据然后再修改页面,如此循环。从性能的角度看,在现代浏览器中单页面Web App已经能够和普通应用程序相媲美,而且几乎所有的操作系统都支持现代的浏览器。使用HTML+CSS+Javascript编写应用程序,能使更多的人们都加入到程序开发的行列。

这足以说明,在Web设计过程中标志着Web将呈现一种新的趋势,它将一个分离的功能层作为API并将表示层用APP的形式体现出来 (HTML5或Native):

  • 单页面+API模式比基于应用程序的HTML多重页面更加灵活,因为底层API可用于多种不同的上下文、形式因素和设备类型。一旦网页内置了API,能够满足客户不同需求(比如合作伙伴vs最终用户)。
  • 该模式意味着本地Web应用能够为用户无论是基于什么平台提供更接近一个本地移动或桌面应用程序的体验。服务平台的移动后端比如StackmobParseAppceleratorKinvey以及Kii借助一些标准的API后端元素可提供跨平台用户体验。
  • 协议(如openAuth (oAuth) 成为作为用户授权的黄金标准已被广泛采用,提供了一个共同的模式。从应用程序和内容中将单独登录/授权问题分离出来。也就是说用户的身份可以从内容、功能和用户体验中清晰地分离出。

一个单页面Web应用程序就是一个Web应用程序,但结构不同。其中最重要的是:在第一次请求的时候,所有的标记语言(HTML)就已经传输到客户端,其余的请求都通过REST API获取JSON数据,数据的传输通过Web Socket API或远程过程调用。单页面应用程序可以说是分拆Web技术的最后一步——通过分离(css)内容,改进架构(XML和 XSLT)上的灵活性,调用服务器(AJAX)再到解压应用程序的导航页面结构。因此,这在Web发展中是个历史性的转折点。

目前这只是单页面Web应用开发的初期,但可以看出将单页面应用、APIs以及JavaScript结合在一起将成为许多流行应用的规范。

所以,当被问到“HTML5是App+API?”,我们会说,“两者皆是——将两者结合在一起要比以往快很多。”单页面应用是一个块非常大的拼图。当然,导航、历史性和SEO等问题也成为单页面Web应用的诟病。


单页面应用的演进

我接触到单页面应用的时候,它看起来就像是将所有的内容放在一个页面上么。只需要在一个 HTML 写好所需要的各个模板,并在不同的页面上 data-role 表明这是个页面(基于 jQuery Mobile)——每个定义的页面都和今天的移动应用的模式相似,有 header、content、footer 三件套。再用 id 来定义好相应的路由。

<div data-role="page" id="foo"> 
...
</div>
  • 1
  • 2
  • 3

这样我们就在一个 HTML 里返回了所有的页面了。随后,只需要在在入口处的 href 里,写好相应的 ID 即可。

<a href="#foo">跳转到foo</a>
  • 1

当我们点击相应的链接时,就会切换到 HTML 中相应的 ID。这种简单的单页面应用基本上就是一个离线应用了,只适合于简单的场景,可是它带有单页面应用的基本特性。而复杂的应用,则需要从服务器获取数据。然而早期受限于移动浏览器性能的影响,只能从服务器获取相应的 HTML,并替换当前的页面。

在这样的应用中,我们可以看到单页面应用的基本元素: 页面路由,通过某种方式,如 URL hash 来说明表明当前所在的页面,并拥有从一个页面跳转到另外一个页面的入口。

当移动设备的性能越来越好时,开发者们开始在浏览器里渲染页面:

  • 使用 jQuery 来做页面交互
  • 使用 jQuery Ajax 来从服务端获取数据
  • 使用 Backbone 来负责路由及 Model
  • 使用 Mustache 作为模板引擎来渲染页面
  • 使用 Require.js 来管理不同的模板
  • 使用 LocalStorage 来存储用户的数据

通过结合这一系列的工具,我们终于可以实现一个复杂的单页面应用。而这些,也就是今天我们看到的单页面应用的基本元素。我们可以在 Angular 应用、React 应用、Vue.js 应用 看到这些基本要素的影子,如:Vue Router、React Router、Angular 2 RouterModule 都是负责路由(页面跳转及模块关系)的。在 Vue 和 React 里,它们都是由辅助模块来实现的。因为 React 只是层 UI 层,而 Vue.js 也是用于构建用户界面的框架。

路由:页面跳转与模块关系

要说起路由,那可是有很长的故事。当我们在浏览器上输入网址的时候,我们就已经开始了各种路由的旅途了。

  1. 浏览器会检查有没有相应的域名缓存,没有的话就会一层层的去向 DNS服务器 寻向,最后返回对应的服务器的 IP 地址。
  2. 接着,我们请求的网站将会将由对应 IP 的 HTTP 服务器处理,HTTP 服务器会根据请求来交给对应的应用容器来处理。
  3. 随后,我们的应用将根据用户请求的路径,将请求交给相应的函数来处理。最后,返回相应的 HTML 和资源文化

当我们做后台应用的时候,我们只需要关心上述过程中的最后一步。即,将对应的路由交给对应的函数来处理。这一点,在不同的后台框架的表现形式都是相似的。

如 Python 语言里的 Web 开发框架 Django 的 URLConf,使用正规表达式来表正

url(r'^articles/2003/$', views.special_case_2003),
  • 1

而在 Laravel 里,则是通过参数的形式来呈现

Route::get('posts/{post}/comments/{comment}', function ($postId, $commentId) {
    //
});
  • 1
  • 2
  • 3

虽然表现形式有一些差别,但是总体来说也是差不多的。而对于前端应用来说,也是如此,将对应的 URL 的逻辑交由对应的函数来处理

React Router 使用了类似形式来处理路由,代码如下所示:

 <Route path="blog" component={BlogList} />
 <Route path="blog/:id" component={BlogDetail} />
  • 1
  • 2

当页面跳转到 blog 的时候,会将控制权将给 BlogList 组件来处理。

当页面跳转到 blog/fasfasf-asdfsafd 的时候,将匹配到这二个路由,并交给 BlogDetail 组件 来处理。而路由中的 id 值,也将作为参数 BlogDetail 组件来处理。

相似的,而 Angular 2 的形式则是:

{ path: 'blog',      component: BlogListComponent },
{ path: 'blog/:id',      component: BlogDetailComponent },
  • 1
  • 2

相似的,这里的 BlogDetailComponent 是一个组件,path 中的 id 值将会传递给 BlogDetailComponent 组件。

从上面来看,尽管表现形式上有所差异,但是其行为是一致的:使用规则引擎来处理路由与函数的关系。稍有不同的是,后台的路由完全交由服务器端来控制,而前端的请求则都是在本地改变其状态。

并且同时在不同的前端框架上,他们在行为上还有一些区别。这取决于我们是否需要后台渲染,即刷新当前页面时的表现形式。

  • 使用 Hash (#)或者 Hash Bang (#!) 的形式。即 # 开头的参数形式,诸如 ued.party/#/blog。当我们访问 blog/12 时,URL 的就会变成 ued.party/#/blog/12
  • 使用新的 HTML 5 的 history API。用户看到的 URL 和正常的 URL 是一样的。当用户点击某个链接进入到新的页面时,会通过 history 的 pushState 来填入新的地址。当我们访问 blog/12 时,URL 的就会变成 ued.party/blog/12。当用户刷新页面的时候,请通过新的 URL 来向服务器请求内容。

幸运的是,大部分的最新 Router 组件都会判断是否支持 history API,再来决定先用哪一个方案。

数据:获取与鉴权

实现路由的时候,只是将对应的控制权交给控制器(或称组件)来处理。而作为一个单页面应用的控制器,当执行到相应的控制器的时候,就可以根据对应的 blog/12 来获取到用户想要的 ID 是 12。这个时候,控制器将需要在页面上设置一个 loading 的状态,然后发送一个请求到后台服务器。

对于数据获取来说,我们可以通过封装过 XMLHttpRequest 的 Ajax 来获取数据,也可以通过新的、支持 Promise 的 Fetch API 来获取数据,等等。Fetch API 与经过 Promise 封装的 Ajax 并没有太大的区别,我们仍然是写类似于的形式:

fetch(url).then(response => response.json())
  .then(data => console.log(data))
  .catch(e => console.log("Oops, error", e))
  • 1
  • 2
  • 3

对于复杂一点的数据交互来说,我们可以通过 RxJS 来解决类似的问题。整个过程中,比较复杂的地方是对数据的鉴权与模型(Model)的处理。

模型麻烦的地方在于:转变成想要的形式。后台返回的值是可变的,它有可能不返回,有可能是 null,又或者是与我们要显示的值不一样——想要展示的是 54%,而后台返回的是 0.54。与此同时,我们可能还需要对数值进行简单的计算,显示一个范围、区间,又或者是不同的两种展示。

同时在必要的时候,我们还需要将这些值存储在本地,或者内存里。当我们重新进入这个页面的时候,我们再去读取这些值。

一旦谈论到数据的时候,不可避免的我们就需要关心安全因素。对于普通的 Web 应用来说,我们可以做两件事来保证数据的安全:

  1. 采用 HTTPS:在传输的过程中保证数据是加密的。
  2. 鉴权:确保指定的用户只能可以访问指定的数据。

目前,流行的前端鉴权方式是 Token 的形式,可以是普通的定制 Token,也可以是 JSON Web Token。获取 Token 的形式,则是通过 Basic 认证——将用户输入的用户名和密码,经过 BASE64 加密发送给服务器。服务器解密后验证是否是正常的用户名和密码,再返回一个带有时期期限的 Token 给前端。

随后,当用户去获取需要权限的数据时,需要在 Header 里鉴定这个 Token 是否有限,再返回相应的数据。如果 Token 已经过期了,则返回 401 或者类似的标志,客户端就在这个时候清除 Token,并让用户重新登录。

数据展示:模板引擎

现在,我们已经获取到这些数据了,下一步所需要做的就是显示这些数据。与其他内容相比,显示数据就是一件简单的事,无非就是:

  • 依据条件来显示、隐藏某些数据
  • 在模板中对数据进行遍历显示
  • 在模板中执行方法来获取相应的值,可以是函数,也可以是过滤器。
  • 依据不同的数值来动态获取样式
  • 等等

不同的框架会存在一些差异。并且现代的前端框架都可以支持单向或者双向的数据绑定。当相应的数据发生变化时,它就可以自动地显示在 UI 上。

最后,在相应需要处理的 UI 上,绑上相应的事件来处理。

只是在数据显示的时候,又会涉及到另外一个问题,即组件化。对于一些需要重用的元素,我们会将其抽取为一个通用的组件,以便于我们可以复用它们。

<my-sizer [(size)]="fontSizePx"></my-sizer>
  • 1

并且在这些组件里,也会涉及到相应的参数变化即状态改变。

交互:事件与状态管理

完成一步步的渲染之后,我们还需要做的事情是:交互。交互分为两部分:用户交互、组件间的交互——共享状态。

组件交互:状态管理

用户从 A 页面跳转到 B 页面的时候,为了解耦组件间的关系,我们不会使用组件的参数来传入值。而是将这些值存储在内存里,在适当的时候调出这些值。当我们处理用户是否登录的时候,我们需要一个 isLogined 的方法来获取用户的状态;在用户登录的时候,我们还需要一个 setLogin 的方法;用户登出的时候,我们还需要更新一下用户的登录状态。

在没有 Redux 之前,我都会写一个 service 来管理应用的状态。在这个模块里写上些 setter、getter 方法来存储状态的值,并根据业务功能写上一些来操作这个值。然而,使用 service 时,我们很难跟踪到状态的变化情况,还需要做一些额外的代码来特别处理。

有时候也会犯懒一下,直接写一个全局变量。这个时候维护起代码来就是一场噩梦,需要全局搜索相应的变量。如果是调用某个特定的 Service 就比较容易找到调用的地方。

用户交互:事件

事实上,对于用户交互来说也只是改变状态的值,即对状态进行操作。

举一个例子,当用户点击登录的时候,发送数据到后台,由后台返回这个值。由控制器一一的去修改这些状态,最后确认这个用户登录,并发一个用户已经登录的广播,又或者修改全局的用户值。

节选自:我的职业是前端工程师


  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值