单页面应用相关一些问题和揣测...

1

处于某些原因, 在一个已经成型的代码库上一个人思索单页面应用的实现方案,
加上之前在学校就想和查找的问题, 涉及到方面已经蛮多了可惜覆盖不到大半,
一些感想记一下:

页面设计和风格

如果是自己做页面, 作为一个文字的爱好者, 文本已经可以完全的替代图标的功能,
那么我完全能接受新的设计潮流里极端的, 提示文本内容和布局本身来体现美感的想法,
而追求极其简洁的功能和页面...

其实是图片太麻烦了, 设计了图片以后, 页面风格更加难控制, 并且图片的使用和部署都不那么方便, 我个人是期待字体图标的方案来解决一些图标的问题, 而图片仅仅是需要大图片时出现.

页面要好看真难, 特别是为了加入各种功能, 界面被撑大, 以及为了界面实现的一些折中,
也许我们会采用 BootStrap 之类的类库来提供布局和基本样式,

可对于一个应用而言, 这是不够的, 应用的界面就像人们对手机应用要求那样做到精致,
以及动画, 渐变颜色等等, 而网页上巨大的界面, 容纳大量的元素, 增加了一些难度.
细节首先难以控制好.(当然和我从业时间短关系也不小...)

然后是设计师的事情.. 不了解.

页面渲染问题

单页面应用的界面, 和传统的桌面平台的 MVC 不同(具体说不上, 没开发过桌面软件),
和服务端的 MVC 也不同, 传统的 Web 应用只要服务端渲染页面发送的浏览器, 浏览器当中只要简单的 jQuery 操作就可以搞定了, 交互性过少, 而这正是单页面应用兴起的原因.

单页面应用几乎把整个 MVC 搬到浏览器里来了, 但一边要面对 DOM 结构臃肿的 View, 一方面 Model 距离数据库又变成大量的网络延迟了.

这里吐槽的是 View 部分. 首先我很想说 DOM 不适合开发应用, 琐碎的问题很多, 比如落后的布局方式造成页面布局大量的 trick, 低性能的 DOM 操作对应用开发上造成的限制, 还有麻烦的 HTML 标记和转义在应用开发中的麻烦. 也不能说做不到, 但是有时候就让人左右为难.

界面开发绕不过 MVC 理论, 开发过其他成熟的平台的同学也许要笑, 前端一片乱的吧(我只能在网上不成系统地搜罗这些资料了, 很凌乱)...

今天看到的幻灯片上讲得清晰多了:

MVC 的架构图, 三者的分离非常明显(问下这是不是动画之类界面的模式呢... 按这样基本上 View 都是要整体刷新的, 而单页面应用这样的形式明显不能直接用):

MVC

Backbone 的模式多少是基于 MVP 的, Backbone.View 的功能和图上一致:

  • View 里的 events 代理的事件用来监听 View
  • View 里的方法通过 jQuery 对 View 进行具体的操作
  • View 对 this.model 的数据进行修改
  • View 通过 this.listenTo 方式监听 Model 里的改变

Backbone 的方式很灵活, 做到了抽取单页面应用的 Common Pattern.
而这足以应付非常多的场景了.

MVP

但是有一点, jQuery 操作 DOM 可是很麻烦的事情.. MVVM 是另一种方式.. 标志性的方案是 ViewModel 和 View 的双向绑定了, 通过框架去掉了 DOM 操作这个复杂的层面, 甚至达到声明式语法编写应用的目的.

当然这仅仅是渲染的部分.. 我最喜欢的 Ractive 声称下一代的 DOM 操作方案, 比较优雅得实现了双向绑定, 可是双向绑定本身仅仅是 View 和 ViewModel 之间的关联, 而没有 Model..(因此又模糊开了...)

Knockout 和 Avalon 是 MVVM 框架, 最吸引眼球的大概还是 AngularJS(不过文档上自己说是 MVC, 非常搞不懂..)

MVVM

换一种角度看, 页面的渲染应该是两种方式的

  • 通过模版引擎整体对界面进行渲染
  • 在已经渲染的界面上, 用 jQuery 局部对界面进行更新

模版引擎的问题是渲染成本高, 破坏体验. 因此比如用 jQuery 或者说 DOM 操作布局更新界面,
实际上 jQuery 操作 DOM 效能更低, 且副作用是 DOM 操作来更新界面存在大量需要维护的状态, 代码复杂, 很容易写出问题.

同时一个问题是, 界面状态的数据都是通过手动操作完成,这样很容易出错, 甚至在 JS 环境和 DOM 各出现一份状态的数据,
那么是按照 DOM 为准吗? DOM 会越来越臃肿, 且操作慢;
按照 JS 环境里的数据吗? 界面才是用户真实看到的, 必须要同步界面上的状态.

结果界面依然是和 JS 运行环境里数据一致的, 一次自动将数据更新到 View 很自然, 很必要.
jQuery 少用吧.

另一个界面的问题是 Web Components, 期待了好多年, 但现状... 能把我等到对前端兴趣全无的样子.

开发环境

单页面应用非常棒的一点是重载代码非常快速, 勉强 CSS 和 JS 可以在调试工具直接更改保存而不用重启的.
当然, 网页开发的主要手段其实还是刷新页面, 有 LiveReload 或者手动, 或者自己写点工具来完成这事情. 首次影响 Node 编程常用 node-dev 这类工具, 保存时马上运行代码.

我在 Ruby 社区和 Go 社区问, 他们似乎都很陌生, 也许难以理解 JS 怎么这样乱来,, 但刷新页面对于 JS 调试来说目前还是重要手段.

相应的开发环境, 监视文件自动刷新页面也就成了必需的. 而其他各种调试工具的搭配, 都要为了页面能尽快刷新来考虑.
比如在调试环境每次修改代码都编译代码(比如 Browserify)就会是比较费事的事情.

对于 CSS 来说, 这种 Live Coding 的特性带来的好处要明显得多.
比如用 Chrome 的 Workspace 功能, 调试工具图形上修改的内容直接能保存下来, 开发非常流畅.
另外是 Brackets 编辑 HTML 和 CSS 的体验, 在编写时马上就能预览页面.

对 JS 来说就麻烦不少了. 仅仅是 stop on exception 和 log 之类的手法. 我个人又不习惯 IDE.

如何更高效地开发

就我个人的感想来说, 人们从前对 DOM 操作的热衷, 和 jQuery 各种复杂的 DOM 操作呼应.
而 DOM 操作就像是命令式编程方案被吐槽的种种弊端一样真实在开发中存在,

比如从界面的一组元素和布局, 切换到一组其他元素和另外的布局,, 这样一个过程,
通过 DOM 操作, 就要去不断思考这里到那里, 中间要做什么, 每个详细的步骤, 非常繁琐;
当我们用了 MVC, 为不同场景设置不同的模版, 甚至相同模版不同参数, 逻辑就清晰很多.

我的理解是, 那种过程式的操作, 在开发中应当尽量转为用数据或者模版表示的状态.
因为要描述一个怎样的状态是什么样子, 要提供怎样的状态的组合, 这还算容易的,
相比来说, 对每个组合, 通过直接的操作来完成时, 就会变得非常复杂.

HTML5 的平台我觉得就是本来不适合做应用, 但大公司为了圈地, 什么都来了.
前端开发存在各种各自为政的工具, 浪费人好多时间去思考界面是怎么做出来的.
将操作抽象成状态来减少操作, 是我现在感到摸索到的一个思路.. 没有验证.

模块化问题

我从前的想法也是, 模块应该像是 Unix 哲学一样, 一个做一件事情, 然后相互组合.
Node 是 Unix 哲学的践行者, 我以前就很想 Browserify 来组织全部的代码.

可慢慢发现这中间真的有问题, 就是, Unix 是管道, 而管道只要有输出输出就可以,
就是说, 根本就不管中间状态是什么样子, 中间完全抽象掉, 封装好就 OK 了.
而界面完全不是这么简单的事情, 比如一个菜单, 它出现在界面就很容易产生关联.

比如说菜单打开了界面上不能同时有多个, 要有联动, 这不能抽象就完事的,
又比如说封装好的菜单应用过来, 但是界面风格和行为在交互上不统一怎么办?
对于服务端的代码来说事情完全不是这样思考的.. 所以前端就是特殊的.

然后是异步加载, 循环引用等等问题.. 讲不来.. Browserify 的同步加载反正局限明显.
就我尝试 Browserify 的经验吧, 管理单页面应用有这样一些问题:

  • CommonJS 规范导致代码中 ../../ 开头的引用大量出现, 很不方便
  • 打包虽然优化得比较快, 但还是有延时, 特别是编译可能出错, 未必注意到
  • 调试一定要打包, Workspace 之类方案用不上了, 比较麻烦
  • 如果拆分组件进行调试, 就需要打包, 甚至打非常多包才能完成

SeaJS 优雅但是不够通用, 我最终还是寄希望 RequireJS 这样大社区的方案.
可惜我已经在 Browserify 方面浪费了太多时间, 现在这边没能赶上..

JS CSS 代码合并问题

选择了模块化的方案, JS 合并也就有现成工具了.
有一点我觉得是, 服务端应该做开发环境和部署环境的逻辑, 而不该 JS 做.
有过纯静态的 HTML 和 JS 合并部署的方案, 使用了 Grunt 流程很麻烦.
我想 Node 好的一点还有部署这种麻烦的事情终于能一个人完整想方案了吧...

关于 CSS, 我尝试了用 Stylus 强大的功能对 CSS 进行快速合并, 不大喜欢, 原因:

  • Workspace 启用后 CSS 编写非常方便, 不用 Stylus 已经能高效了
  • 目前我调试时是在 HTML 引入单个合并和编译完成的 CSS 文件, 很不方便
  • 开发中考虑语法复杂和同事的习惯不会去用 Stylus 的高级语法, 其实还是 CSS

未来我想切换到通过 CSS @import 标记编译的方案, 希望能好些..
同样, 考虑到要尝试模块化的开发, 用分散的文件简单直接!!

CDN 部署和递归编译

感觉公司的前端工作流程太过原始, 工作经验半年多无力搞定...
网站加速用到了 CDN, 以及永久缓存带来的文件名替换问题, 目前没采用 FIS 那样高级的方案.
如果能递归编译对文件名进行替换, csssprites 等文件名问题也就小多了,

但目前常用的技术对文件名死死地依赖, 导致前端环境各种不灵活,
特别是对组件进行模块化的时候, 如果想大改项目文件结构, 就会有这问题,
出于某原因项目应该很快速推进时, 常常被这些问题卡着实在有些恶心.
我认为单页面应用是个大坑, 原因很大程度是因为这些基本问题没解决好.

其他

我还没学数据处理部分的, 现在目测属于服务端编程方面的知识才对. 没学就只能吐吐槽吧..


返回博客首页: http://blog.tiye.me


如果觉得我的文章对你有用,请随意赞赏

你可能感兴趣的

寸志 · 2013年11月21日

backbone的mvc太他妈简单了,就是个深坑

回复

heroicyang · 2013年11月21日

同样处于摸索中的简单谈下我的看法。

  1. 对于 MV* 的选择,其实我是偏向 Backbone 的。虽然看上去需要自己做的事情较多,但是我个人是不太喜欢往 DOM 结构里添加一大堆 Attribute 的,所以 Angular、Ember 之流我没去碰过,而且还得让前端去学习 AOP、Inject 之类,似乎以后前端挑战会越来越大。但 Google 貌似是大步的迈在这条路上。

  2. 关于 Backbone 中 DOM 结构的刷新,其实我一般是局部 detach element, 然后 remove 掉其关联的事件,然后再用模板渲染好数据整体插入进来,重新做一次 Backbone 的事件绑定。

  3. 关于开发环境,除了很多打包、编译的工作交给 Grunt 以外,我竟然还在采用手动 F5 刷新页面,哈哈,原始了。

  4. 模块化这个,其实用 Backbone 是很好将 js 模块化的。比如你举的栗子,一个菜单它是完全可以封装为模块的,让它只具备其菜单的行为就 OK,然后引入事件或者消息发布订阅模式来解耦。

  5. CSS 我还是采用了 stylus + nib 来搞,毕竟减少工作量,虽然打包为一个文件貌似不太方便调试。。。

  6. 最好扒拉一下关于 RequireJS 吧,说实话项目除非大得不得了,不然一般最后线上环境都是要打包为一个 js 文件的,这个时候所谓的异步模块加载完全没用了。所以我一般只用 RequireJS 来实现所谓的 Modular ...这个你可以看下我开那个项目 CNode Club 最近的一些前端代码。

  7. 话说给个 IM 之类的联系方式呗,我最近又很少泡 CNode 社区之类,虽然我微博 fo 了你,但是总觉得要交流还是 IM 之流方便些啊。。。

回复

题叶 作者 · 2013年11月21日

1. 关于 MV* , Angular 我也觉得太大, 在我的场景里不需要. 单纯双向绑定我一直热衷使用 Ractivejs, 上手的感觉就和平常模版引擎类似的 2. 略 3. 页面刷新我当前的项目方案是 grunt-contrib-watch 加上 LiveReload. 但个人用的话我有自己的模块, 使用更舒服些, 不过细节我也没做好. [doodle](https://github.com/jiyinyiyong/doodle) [doodle-crx](https://github.com/jiyinyiyong/doodle-crx) 4. 自己越折腾越感到 Backbone 的消息模式设计非常不错, 对代码模块化和解耦非常有效. 但是出于界面操作上复杂度来说, 我急于找强大的解决方案. 5. 略 6. 公司的项目还是有必要考虑异步加载的, 我现在也是往 RequireJS 去赶. 7. 微博上说啰.

回复

weakish · 2013年12月25日

提示文本内容和布局本身来体现没美感的想法

s/没//

课实际上 jQuery 操作 DOM 效能更低

s/课/可/

我以前就很像 Browserify 来组织全部的代码

s/像/想/

遮不能抽象就完事的

s/遮/这/

回复

题叶 作者 · 2013年12月25日

个别错字已更新, thx

回复

载入中...