前端程序猿必知:单页面应用的核心

这几年里。单页面应用的框架使人目不暇接,各类新的概念也层出不穷。从过去的 jQuery Mobie、Backbone 到今天的 Angular 二、React、Vue 2,除了版本不一样,他们还有很是多的一样之处。php

刚開始写商业代码的时候,我使用的是 jQuery。使用 jQuery 来实现功能很是easy,找到一个对应的 jQuery 插件,再编写对应的功能就能够。css

对于单页面应用亦是如此,寻找一个相辅助的插件就可以了,如 jQuery Mobile。前端

虽然在今天看来。jQuery Mobile 已经不适合于今天的多数场景了。这个主要缘由是,当时的用户对于移动 Web 应用的理解和今天是不一样的。他们认为移动 Web 应用就是针对移动设备而订制的。移动设备的 UI、更快的载入速度等等。python

而在今天,多数的移动 Web 应用,差点儿都是单页面应用了。git

过去。即便咱们想建立一个单页面应用,可能也没有一个合适的方案。而在今天,可选择的方案就多了(PS:參见《第四章:学习前端仅仅需要三个月【框架篇】》)。每个人在不一样类型的项目上,也会有不一样的方案,没有一个框架能解决所有的问题github

  • 对于工做来讲。我更但愿的是一个完整的解决方式。
  • 对于编程体验来讲,我喜欢一点点的去创造一些轮子。

当咱们会用的框架越多的时候, 所花费的时间抉择也就越多。而单页面应用的都有一些一样的元素。对于这些基本元素的理解,可以让咱们更快的适合其它框架。编程

单页面应用的演进

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

再用 id 来定义好对应的路由。浏览器

<div data-role="page" id="foo"> 
...
</div>

这样咱们就在一个 HTML 里返回了所有的页面了。缓存

随后,仅仅需要在在入口处的 href 里,写好对应的 ID 就能够。

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

当咱们点击对应的连接时,就会切换到 HTML 中对应的 ID。这种简单的单页面应用基本上就是一个离线应用了。仅仅适合于简单的场景,可是它带有单页面应用的基本特性。而复杂的应用。则需要从server获取数据。然而早期受限于移动浏览器性能的影响,仅仅能从server获取对应的 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. 浏览器会检查有没有对应的域名缓存,没有的话就会一层层的去向 DNSserver 寻向,最后返回对应的server的 IP 地址。

  2. 接着,咱们请求的站点将会将由对应 IP 的 HTTP server处理。HTTP server会依据请求来交给对应的应用容器来处理。

  3. 随后。咱们的应用将依据用户请求的路径,将请求交给对应的函数来处理。最后,返回对应的 HTML 和资源文化

当咱们作后台应用的时候。咱们仅仅需要关心上述过程当中的最后一步。即,将对应的路由交给对应的函数来处理。

这一点。在不一样的后台框架的表现形式都是相似的。

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

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

而在 Laravel 里,则是经过參数的形式来呈现

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

虽然表现形式有一些差异,可是总体来讲也是差点儿相同的。

而对于前端应用来讲,也是如此,将对应的 URL 的逻辑交由对应的函数来处理

React Router 使用了相似形式来处理路由。代码例如如下所看到的:

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

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

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

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

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

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

从上面来看。虽然表现形式上有所差别,可是其行为是一致的:使用规则引擎来处理路由与函数的关系。

稍有不一样的是,后台的路由全然交由server端来控制,而前端的请求则都是在本地改变其状态。

并且同一时候在不一样的前端框架上,他们在行为上另外一些差异。这取决于咱们是否需要后台渲染,即刷新当前页面时的表现形式。

  • 使用 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 来向server请求内容。

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

数据:获取与鉴权

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

对于数据获取来讲,咱们可以经过封装过 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))

对于复杂一点的数据交互来讲,咱们可以经过 RxJS 来解决相似的问题。整个过程当中,比較复杂的地方是对数据的鉴权与模型(Model)的处理。

模型麻烦的地方在于:转变成想要的形式。

后台返回的值是可变的,它有可能不返回。有多是 null,又或者是与咱们要显示的值不同——想要展现的是 54%,然后台返回的是 0.54。与此同一时候。咱们可能还需要对数值进行简单的计算。显示一个范围、区间,又或者是不一样的两种展现。

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

一旦谈论到数据的时候,不可避免的咱们就需要关心安全因素。

对于普通的 Web 应用来讲,咱们可以作两件事来保证数据的安全:

  1. 採用 HTTPS:在传输的过程当中保证数据是加密的。

  2. 鉴权:确保指定的用户仅仅能可以訪问指定的数据。

眼下。流行的前端鉴权方式是 Token 的形式。可以是普通的定制 Token,也可以是 JSON Web Token。

获取 Token 的形式。则是经过 Basic 认证——将用户输入的username和password,通过 BASE64 加密发送给server。server解密后验证是不是正常的username和password,再返回一个带有时期期限的 Token 给前端。

随后,当用户去获取需要权限的数据时,需要在 Header 里鉴定这个 Token 是否有限。再返回对应的数据。假设 Token 已通过期了,则返回 401 或者相似的标志。client就在这个时候清除 Token。并让用户又一次登陆。

数据展现:模板引擎

现在,咱们已经获取到这些数据了,下一步所需要作的就是显示这些数据。

与其它内容相比。显示数据就是一件简单的事,无非就是:

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

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

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

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

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

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

交互:事件与状态管理

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

组件交互:状态管理

用户从 A 页面跳转到 B 页面的时候。为了解耦组件间的关系,咱们不会使用组件的參数来传入值。

而是将这些值存储在内存里,在适当的时候调出这些值。当咱们处理用户是否登陆的时候。咱们需要一个 isLogined 的方法来获取用户的状态。在用户登陆的时候。咱们还需要一个 setLogin 的方法;用户登出的时候,咱们还需要更新一下用户的登陆状态。

在没有 Redux 以前。我都会写一个 service 来管理应用的状态。在这个模块里写上些 setter、getter 方法来存储状态的值,并依据业务功能写上一些来操做这个值。

然而,使用 service 时。咱们很是难跟踪到状态的变化状况。还需要作一些额外的代码来特别处理。

有时候也会犯懒一下,直接写一个全局变量。

这个时候维护起代码来就是一场噩梦,需要全局搜索对应的变量。

假设是调用某个特定的 Service 就比較easy找到调用的地方。

用户交互:事件

其实,对于用户交互来讲也仅仅是改变状态的值。即对状态进行操做。

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

节选自:个人职业是前端project师

相关文章
相关标签/搜索