nireader的前端

nireader的项目始于五月份,迫于进度,google reader关闭之时勉强上线。未经好好思考组织的结构迅速暴露出大把问题,痛定思痛之下,开始相对很彻底的重构。直至最近才算结构基本成型,reader本身也具备一定的可用性,我自己现在就用着。

链接:

代码

nireader

(测试账号 test:123456, chrome, please)

整理最初的原则:

  1. Single-page application的优点,避免刷新页面的内容切换

  2. 正常网页浏览的优点,状态(页面内容)与url对应,可由url恢复,支持后退、前进等

  3. 简单,干净

  4. 不考虑浏览器兼容

根据上述目标初步确定关键行为的实现方案:

  1. 采用history的pushState方法实现url的跳转

  2. 采用js渲染页面内容

(1、2放在一起很容易联想到所谓pjax,这是个现成的方案,但不够好)

  1. 作为2的基础,采用ajax获取结构化数据

  2. 在上述前提下,为避免逻辑冗余,将业务逻辑放在前端

一些考虑:

  1. 为什么后台不做内容渲染,这样可以加快页面初始渲染速度

前后端均做内容渲染造成业务逻辑冗余,不舒服

  1. 要避免业务逻辑冗余,为什么不把由结构化数据渲染为html块的事全部交给后端,js直接通过接口获取html片段并填充?

最初的原则1决定了大量逻辑在于前端(否则依赖大量ajax请求,不可忍受),这要求js掌握足够多的数据(结构化的)。这样我需要两套api,一套返回结构化数据,一套返回结构化数据渲染的html内容,太二了。而且业务逻辑与页面结构的关系及其紧密,将二者分别放在前后两端容易混乱。综上,我更倾向于服务端不掺和业务、页面逻辑,仅仅做两件事:1)响应访问页面的请求,返回页面框架,2)通过api提供具体数据。以上也正是我无法直接套用现有的pjax方案(貌似github就是用的这个?)的原因。

结构图:

结构图

具体实现:

使用sea.js实现模块化开发,spm进行构建。

一类页面对应一类page(page/下一个模块),如reader

reader调用stateManger,page(1中page的内容容器,其角色更准确的叫法是contentManager),floater等独立于页面状态的功能模块

stateManager管理url跳转/浏览器history,page管理页面的内容,reader管理二者间逻辑,如监听stateManager的url跳转事件,调用page的checkout方法进行内容的切换

stateManager提供主动跳转的接口并提供一些行为事件(如跳转)

floater是一个典型的(也是目前唯一的)url地址、页面内容无关的模块,实现一个全局快捷键调出的浮层。浮层之上的input目前接受命令(命令提示、补全、执行)、关键词检索(channel,...)、url订阅添加等,我觉得这个比可视化菜单方便太多,是以后可做工作最多的一个部分,也将成为整个reader功能最强大、最具特色的一个部分。

url与页面内容(content)对应,page(内容管理器)通过获取当前url,初始化对应的content,并将dom作为参数传入

每个content中包含具体的页面逻辑,做的事对应传统web页面的所有js行为(类似某a页面的main.js那种)。区别在于,后者在页面加载时浏览器主动运行;而content的初始化与执行是由page在checkout操作时拉起的。content可以调用stateManager的跳转接口(通过全局事件)实现url跳转。

再向下的结构就是传统前端开发中很熟悉的东西,cache、request、template、event等。

eventList模块提供构建特定事件集合的接口,集合提供add、remove及clear等方法,意义在于收集content运行时的所有事件绑定,在切换页面内容对旧content进行清除时移除所有事件,避免残留事件绑定的影响。

resource模块管理所有资源类数据(如channel信息,文章信息,channel列表、文章列表等),单独把这些数据拿出来的原因是,1)资源获取方式基本类似,用起来方便,2)可以被缓存,并作管理。

cache模块用于缓存,目前是缓存在内存中,并限制规模,定时整理。这边的实现(包括存储结构、缓存淘汰队列)还很简陋,可优化空间很大。以后还可以通过localStorage实现持久化或二级缓存。

request模块管理所有ajax请求。这是一段历史悠久的代码,从在qiniu开始,到现在一直在用,偶尔会做改进。特点是请求失败后自动进行有限次重试。

结果:

开发时使用方便考虑,jquery等第三方库未模块化,被一起塞进一个lib.js

页面的代码构建完成后在dist/page/下,如reader类页面为dist/reader.js(目前只此一类页面),该代码的执行由页面中script驱动:

script.
    // Set configuration
    seajs.config({
        base: '../javascripts/dist/'
    });

    // For development
    if (location.href.indexOf('?dev') > 0) {
        seajs.use('/javascripts/src/page/reader', function(reader){
            reader.bind();
        });
    }
    // For production
    else {
        seajs.use('/javascripts/dist/page/reader', function(){
            seajs.use('nireader/nireader-fe/2.0.0/page/reader', function(reader){
                reader.bind();
            });
        });
    }

页面所有内容:

页面文件

特别的:

  1. 页面上方上级跳转,页面左右平级跳转

  2. Esc调出全局命令

  3. 部分链接内容预加载,如相邻文章内容、频道信息,配合缓存机制实现较差网络状态下的良好体验

  4. 文章阅读时滚动翻页