一个单页应用解决方案

Posted 网页制作

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一个单页应用解决方案相关的知识,希望对你有一定的参考价值。

现状

随着AJAX技术的普及,现在的网站体验越来越好了,不需要刷新页面就能做很多事了,甚至整个网站就只有一个页面,像原生的应用一样,有非常流畅的操作体验。 然而,开发出这样的网站并不容易,会有很多技术上的问题。

庆幸的是,现有的许多框架和库在解决这样的问题,例如Angular、React、Vue,它们各成体系,拥有一全套的技术栈。你可以选择其中一个,参考示例和文档,不难做出一个有一定规模的单页应用。 这类框架主要是解决了开发中需要做大量意义不大的DOM操作的问题,从而可以让开发者把更多的精力放在业务逻辑上,降低复杂度。 它们也都有各自的组件实现方式,从而能将一些小功能解耦复用,这是组建大型项目的基础。 在这基础上,自然而然就会有了许多相关的库和工具,比如单页应用非常需要的路由实现,资源的打包整合等等。

对于一个网站而言,环境问题是不能忽视的,用户要通过浏览器,先把需要的程序代码和资源下载下来,然后等浏览器将界面呈现出来后才能进行交互。 对于一个大型应用来说,代码量不可避免地会到达一定的规模,需要的静态资源也不在少数,不可能在一开始就全部加载的,用户一定会等得不耐烦的,很有可能在加载到一半时刷新页面或者离开。 如果用户放弃了,一切都等于0,所以这是一个需要认真考虑的问题。当然,这样明显的问题,现在肯定已经有了成熟的解决方案,例如或,在首页时不加载所有代码,仅在用到的时候再加载。

现在的框架和库已经比较好地解决了大部分问题了,但是引入框架和库是会增加项目的复杂度的。 比如说,使用Angular和Vue的双向绑定后,虽然不需要为DOM操作操心了,但是如果框架出现了BUG,将很难及时解决,因为不可能去修改框架代码的,这不是说开发者能力不够,而是时间成本问题。 凡事都有两面性,使用这类框架确实能带来许多便利,但同时也不避免地带来一些问题。有时页面并不复杂,只是简单的一个列表,或是展示一下信息,没有很多的交互,这时就要慎重考虑引入这些框架是否得不偿失了。

还有一点是,使用这些框架大多数时候需要花不少时间进行配置才能开发,折腾这些配置虽然不难,但是读各种工具库的文档也是很费时间的。 这也是开发技术的进步与浏览器的局限性的矛盾,需要构建工具将源代码转换成浏览器能识别的代码。

我的解决方案

我一直在思考如何才能更好地解决这些问题,做到既能开发简单,又比较好部署,最终也能达到良好的用户体验。于是我设计了一个新的框架:。 这个框架做的工作就是整合按需加载和路由功能,做到不需要配置构建脚本就能进行开发,并且为优化部署留有余地。

按需加载模块的改进

按需加载其实是和模块化分不开的,实现按需加载,就要先做模块化。出于加载速度和部署的考虑,我将模块分成了声明和定义两个部分。例如:

// 模块声明

{

name: 'views.TodoList',

src: '/views/TodoList.html',

type: 'page',

require: ['jquery', 'utils.store', 'utils.render']

}


// 模块定义

define('views.TodoList', function(require, exports, module) {

var $ = require('jquery');


exports.foo = function() {};

});


按AMD标准的话,一个文件一个模块,在加载时,只能先加载这个模块,然后才能得知它的依赖模块并继续加载。如果依赖层级稍多,那么整体速度就会慢下来。 所以一开始先对全部模块进行声明,让加载器预先知道这个模块的依赖项,就能够进行并发加载了。 另外,我将模块设计为以自定义的模块名为唯一标识的模块,与文件位置无关,这样做的好处是可以对各模块的定义文件进行压缩,在部署时简单替换掉原来声明中的路径就可以了。 如果项目不大的话,也能非常方便将这些模块合并成一个文件。

另一个有意思的事情是,前端不只有JS,往往是需要CSS样式和HTML模板的,如果将它们完全分离,其实维护起来并不容易,需要花时间找到对应的文件; 另外对JS而言,CSS和HTML文件并不能直接引入到代码中,这样一来依赖关系就不明确了,模块化只能对JS而言,与CSS和HTML没有约束关系。 为了解决这个问题,我设计了一种可以包含CSS和HTML等资源的模块定义方式,就是用HTML文件来定义。 这样可以在style标签写样式,script标签中写业务逻辑,还可以添加HTML页面模板,既解决了资源整合的问题,也能有很好的编辑器支持。

路由的改进

现有的一些路由实现虽然已经很不错了,但它们难以解决以下的问题:

  • 依次进入页面:R(root) -> L(list) -> D(detail),如果想从D直接返回R,这是很难实现的。 如果使用history.go(-2),虽然从R开始依次进入的情况还可以处理,但有机会直接进入L或是D时,就行不通了。 而如果直接跳到R,那相当于是:R -> L -> D -> R,这不是返回了,后退的时候还能从R返回到D,很奇怪。

  • 如果从A页面进到到B页面,B是一个带tab导航的,在B页面的tab中切换了几次后:A -> B -> B1 -> B2 -> B1 -> B2,再想返回A就很困难了。

  • 从列表页L进入详情页D,再返回,不好实现保持L的原来状态,尤其是滚动条位置。 现有的框架只能这样做:离开前先把L的数据和状态存起来,记录滚动条位置,然后重新进入L时,读取一下有没有记录,如果有,取出来恢复并清空,如果没有,从服务器获取数据。 这很麻烦,而且很容易出错。

针对实践中遇到的这些问题,我将路由设计为是分层级的,层级会影响前进后退的行为。 我只提供了一个方法切换路由,这个方法只需要一个url参数,切换时框架会做处理:

  • 切换路由时,会保留原来的页面。

  • 切换时先查询历史记录,如果页面已存在,则返回到这个页面,并将这个页面之后访问的页面都清除掉。

  • 如果是从子路由切换到父级路由,并且历史记录不存在,会替换当前的记录为父级路由,即无法通过后退返回到原先的子路由。

为了便于控制页面和实现动画,我在页面的状态改变时(创建、暂时离开、恢复、销毁),会改变页面的类,并提供事件处理函数。 如果一个页面有做消耗性能或影响全局的监听和轮询,可以在切换页面的时候移除,切回页面的时候恢复。

现阶段这个框架还处于刚开发的阶段,可能还有很多不够完善的地方,我会将它投入实战,在实战中不断改进完善。 如果有读者在尝试使用它,非常欢迎到这个项目的issue中提功能需求或BUG。


以上是关于一个单页应用解决方案的主要内容,如果未能解决你的问题,请参考以下文章

[Angularjs]asp.net mvc+angularjs+web api单页应用之CRUD操作

hbuilder+vue单页应用打包成APP后退按钮返回上一页的问题

使用 Vue + Flask 搭建单页应用

前端 SPA 单页应用数据统计解决方案 (ReactJS / VueJS)

Vue 创建多页面应用模式

原创Ionic单页应用跳转外链,构造路由返回的解决办法及代码