码迷,mamicode.com
首页 > 其他好文 > 详细

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

时间:2017-08-08 19:51:17      阅读:174      评论:0      收藏:0      [点我收藏+]

标签:null   基本特性   uil   mobile   跳转   了解   javascrip   部分   登录   

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

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

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

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

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

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

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

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

单页面应用的演进

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

再用 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师

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

标签:null   基本特性   uil   mobile   跳转   了解   javascrip   部分   登录   

原文地址:http://www.cnblogs.com/brucemengbm/p/7308029.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!