一:前言
2015年12月25日,微信2.3版本成功上线,后台Struts,前端jsp。关注用户数突破50万。
2016年4月29日,微信2.4版本成功上线,后台Struts,前端HTML视图模板,请求全部ajax异步完成,页面真正实现无刷新。
二:心路历程
2015年有一次去恒生电子跟学长(7年前端从业经验)交流HTML5时,两人针对是否自主开发维护前端控件展开了讨论,学长一直建议学习使用现成的成熟控件,拿来主义;我却沉浸在自己写一些小控件的世界里面。其实市面上的前端框架品类非常多,我们根本来不及一一学习,从早期的prototype,mootools,YUI,到后来的jquery,requireJs,seaJs,angularJs,bootstrap等等真的好多好多,每个框架都有各自的优点。跟学长讨论下来,我也慢慢认可他的观点了,在学习认知阶段确实应该尝试封装控件,学习底层原理,毕竟每个框架都是基础语法封装而成,当从业4~5年之后应该要考虑控件稳定性和维护成本,以我们的自己封装的控件虽然轻量,但是需要投入比较大的维护成本,而且是你个人或者小团队产出,这样难免会有未曾考虑到的场景。所以直接学习使用社区成熟的控件可以大大提高产品稳定性,遇到问题也方便到git或者社区里面参与讨论寻求答案,不过使用的同时也不能忘记学习,因为控件都离不开基础的知识,当我们控件用久了之后,肯定会碰到边缘冷门问题,相信我,肯定会遇到,这时候如果基础扎实就能大大提高定位问题解决问题的速度。结束前学长向我推荐了Framework7(F7)前端框架,恒生电子现在有一款新的金融app已经开始采用这套框架进行尝试开发,并展示了这款半成品的native应用,机型是iphone5s,测试下来,功能点的页面切换非常流畅,并且得知,这套框架在ios下面操作无卡顿,在Android低版本手机会出现卡顿,考虑到目前市面的Android手机一般性能都不会太低,于是他们采用了F7框架。
其实当时学长演示的时候,我就在脑海里琢磨这套框架效果逼近native,如果用到自己公司的杭州市民卡微信会怎么样,效果当然非常炫酷,我自己盲目的肯定了它。为了肯定这份盲目,于是2015年下半年开始抽空学习F7框架。F7框架最初是由国外idangero团队出品,后来淘宝对其官网进行翻译,当然里面一些冷门的API还没有翻译。
时间马上到2015年11月份,启动微信2.3版本开发,此时考虑到后台小伙伴工作比较繁忙而且自己对F7的实战还偏少,于是直到微信2.3成功上线之后,我才联系部门经理和产品经理一起提出自己关于微信2.4前端彻底改版的想法,希望采用MVC的前端框架,让用户感觉我们的微信公众号跟app的体验类似,抛弃现行的jsp页面,使用HTML+AJAX异步模式,视图用静态HTML,数据全部通过json传递,前端的业务控制全部放在js里,后台单纯负责接受参数,返回数据。并把前段时间自己的一些F7的实践和利弊一并抛出来,产品比较关心适配性,并表示Android中高端机型不卡顿就可以接受,部门经理也非常支持我,决定调动后台一起全力配合改版微信2.4,好了,一切就绪,就等结果啦!!
经过整个团队2个月的辛勤加班加点,修改完成128个bug,终于在2016年4月29日成功上线全新的微信2.4版本,看着F7框架在线上稳定的运行,自己内心也充满了成就感。这个版本出来以后,意味着以后只要后台逻辑不变,前端表现层的更新升级,都可以在HTML视图模板上进行,彻底解耦!
bug统计图如下:
三:F7框架小结
1:在前端渲染引擎上不需要单独引入juice或者其它框架,F7自身集成了template7前端引擎,让我们在组织json数据的时候非常方便,使用方法类似普通引擎,但功能更加强大,具体详见官网地址:idangero.us/template7/
2:添加主视图时,设置domCache:true,可以对二级以上页面进行缓存,大大减少服务器请求,提升用户返回速度。
3:框架默认对a标签的href做了处理,所有地址全部默认采用ajax载入载入,如果特殊页面需要跳出框架,API告诉我们只要给a标签加上class=external即可。但是在微信2.4中我没有用到external,使用这个样式名之后整个页面会被刷新,之前缓存的DOM片段会被清空,当我们返回的时候,DOM将需要重新请求。于是自己使用事件代理封装了一个iframe的跳转,当a标签含有class=frame_detail时,让它默认跳转到一张iframe的视图,然后把url里面的外链地址传到iframe的src属性中。截止目前,资讯详情、地图详情、协议详情这三个跳转都是iframe方式,保留DOM片段,大大提高用户返回速度。
4:因为公司配备专业的UI工程师,很多F7框架里面的控件需要样式覆盖,这时候新建一个screen.css来进行样式重载,不要去修改F7的自身样式,上线的时候再用grunt把所有css和js压缩合并,切记合并css的时候一定是F7自身css在先,自定义的css在后。
5:载入ajax数据和滑动翻页使用比较频繁,自己在原先框架基础上针对市民卡项目特性,又封装了ajaxGetData、ajaxNextPage两个方法,分别是拿到数据渲染和翻页功能,暴露合理参数,方便各个页面场景调用。
方法代码:
//ajaxGetData
app.ajaxGetData = function(config){
var a = new Date();
b = a.getTime().toString(),
date = b.slice(0,-3),
data = config.data || function(){return{}},
url = config.url,
view = config.view,
fn = config.callback || function(){},
el = '.page[data-page="' + config.target + '"]',
el = $$(el),
current = el.find('input.currentPage'),
currentPage = parseInt( current.val() || 0),
max = el.find('input.maxPage'),
pageno = config.pageno || 'pageno',//请求页码字段
wrap = config.wrap || $$(el).find('.list-block ul'),
data = data();
if(AJAXDATE == date){ //设置全局变量,当滚动惯性的时候,点击切换标签,不进行数据加载,把毫秒转换成秒,同一秒只发送一次请求。
fn();
return
}
AJAXDATE = date;
if( data.currentPage ){
data[pageno] = data.currentPage;
}
else{
data[pageno] = ++currentPage;
}
$$.ajax({ //ajax回调完成之后,再把返回的数据填进去
async:true, //异步请求
method:'GET',
url: url+'?'+date, //增加随机数防止请求缓存
data: data, //模拟传递参数
dataType:'json',
success:function (data) {
// 生成新条目的HTML
var html = '';
current.val(data['pageno']);
max.val(data['totalpage']);
html = SMK.templates[view](data);
// 添加新条目
wrap.append(html);
fn();
}
});
}
//ajaxNextPage
app.ajaxNextPage = function (config){
var data = config.data || function(){return{}},
url = config.url,
view = config.view,
tips = config.tips || '已经没有更多了~',
loading = false,
el = '.page[data-page="' + config.target + '"]',
el = $$(el),
current = el.find('input.currentPage'),
max = el.find('input.maxPage'),
// 注册'infinite'事件处理函数
$$(el).find('.infinite-scroll').on('infinite', function () {
// 上次加载的序号
var currentPage = parseInt(current.val() || 1),
// 最多可加载的条目
maxPage = parseInt(max.val() || 2);
// 如果正在加载,则退出
if (loading) return;
// 设置flag
loading = true;
// 模拟200ms的加载过程
//setTimeout(function () {
if (currentPage >= maxPage) {
// 加载完毕,则注销无限加载事件,以防不必要的加载
myApp.detachInfiniteScroll($$('.infinite-scroll'));
loading = false;
// 删除加载提示符
$$(el).find('.infinite-scroll-preloader').remove();
$$(el).find('.infinite-scroll').append('<p class="tips_nomore" >' + tips + '</p>');
return;
}
app.ajaxGetData({
target:config.target,//当前点击的对象 String
view:view,
data:data, // 传递给后台的数据 Object
wrap:config.wrap || $$(el).find('.list-block ul'),
callback:function(){loading = false;},//loading传值进入ajaxGetData之后不能反向映射到外面来,所以使用回调函数
url:url, //请求地址 String
pageno:config.pageno || 'pageno',//请求页码字段
});
// }, 200);
});
}
5、上述都是个人的一些理解,欢迎正在使用F7框架的小伙伴不吝指正。
四:参考资料: framework7-cn-bbs,framework7英文官网,framework7淘宝团队翻译