- 本文为个推投稿,根据个推 Web 前端首席架构师姜季廷在 Meetup 野狗技术沙龙第三期的技术分享整理而成。他从 Angular 的特性、组件化之路以及个推云组件最佳实践和经验分享等维度为大家解读了基于 AngularJS 的个推前端云组件。
- 欢迎技术投稿、约稿、给文章纠错,请发送邮件至 mobile@csdn.net。
AngularJS 是 Google 设计和开发的一套前端开发框架,它能帮助开发人员更便捷地进行前端开发。AngularJS 是为了克服 HTML 在构建应用上的不足而设计的,非常全面且简单易学习,因此 AngularJS 快速地成为了 Javascript 的主流框架。
一、Amazing 的 Angular
AnguarJS的一些特性
- 方便的 REST:RESTful 逐渐成为了一种标准的服务器和客户端沟通的方式。你只需使用一行 Javascript 代码,就可以快速地从服务器端得到数据。AugularJS 将这些变成了 JS 对象,作为 Model,遵循 MVVM(modelview view-model)设计模式。
- MVVM 救星:Model 将和 ViewModel 互动(通过
$scope
对象),并监听 Model 的变化。可以通过 View 来发送和渲染,由 HTML 来展示代码。View可以通过$routeProvider
对象来支配,所以你可以深度地链接和组织你的 View 和 Controller,将它们变成导航 URL。AngualrJS 同时提供了无状态的 Controller,可以用来初始化和控制$scope
对象。 - 数据绑定和依赖注入:在 MVVM 设计模式中的任何东西无论发生任何事情都自动地和 UI 通信。这帮助我们去除了
wrapper
、getter/setter
方法或者class
定义。AngularJS 将帮助我们处理所有的这些内容,你可以处理数据像处理基本 Javascript 数据类型般。当然你也可以通过自定义处理复杂数据。正因为所有事情的发生都是自动的,所以你不必调用一个main()
来执行你的代码,而是通过依赖关系来驱动。 - 可扩展的HTML:大多数网站都是使用非语义的
<div>
标签来搭建的。你需要自己在 CSS 的class
中定义相关的 DOM 层次结构。而使用 AngularJS,你可以像操作 XML 一样操作 HTML,有无穷的方式来完成标签和属性定义。AngularJS 通过自己的编译器和 directives 来完成相关的设置,而这也是组件实现的基石。
大家接触jQuery的时候发现,要做事先绑定,取回数据要塞回去,塞的过程都是要自己关心的。但是利用 Angular,数据取回来只要注入变量自动完成了,包括事件绑定。数据绑定,MVVM、依赖注入让你觉得,原先要关心很多东西,现在都不需要关心了,只需更加关心数据结构和业务层,它把我们从繁琐 DOM 绑定中解脱出来。
二、组件化之路
组件是对数据和方法的简单封装,在此样式类,指令型等具备UI效果的组件,方法等统称组件。在大型软件中,组件化是一种共识,它一方面提高了开发效率,另一方面降低了维护成本。
组件化及组件展现形式
组件化可以有很多事情可以做,比如模板化,现在模板化重任交到前端。第二个是公共样式库,第三公共函数库,一些业务组件,模块化特殊一点。
组件大概展现形式包括:统一的样式库,具有一定 HTML 结构的代码片段,具有一部分 JS 控制的功能函数,具有一定数据输入和输出的控制。
三、揭开云组件的面纱
云以及云组件概念
云是网络和互联网的一种比喻说法。过去往往用云来表示电信网,后来也用来抽象地表示互联网和底层基础设施。
云服务指通过网络以按需、易扩展的方式获得所需服务。这种服务可以是 IT 和软件、互联网相关,也可是其他服务。它意味着计算能力也可作为一种商品通过互联网进行流通。把云和组件二者结合则构成了云组件。说到底是希望通过一个统一的控制的东西,把 N 个项目全部控制在一起。
个推的组件类型
个推的组件类型包括样式类组件、指令型组件、服务型组件、公共过滤器、公共函数库等。
从组件分类里可以发现专属 CSS 是样式类组件,加上模板就是非常简单的组件,再把加控制器放进去,就是前面讲的具有一定 JS 和一定逻辑的组件,把 link 加进去,带了动画,数据层加进去就具备一定的输入输出能力。这个数据层可能包含多种,有可能是跟你的页面控制器交互,也有可能这个组件非常强,自己直接与服务端通信获取数据和传递数据(当然实际实践中可能前者更合适当前我们的环境,后者对统一的接口要求会更高)。
上图是个推云组件的技术方案。基于前端三大件和一些其他库比如地理围栏的组件(需要让百度地图给我们整个项目对接起来),还有可视化的项目,比如 G20 期间杭州某景区人流情况,可视化项目会用到第三方库。
根据云组件的一些情况,我们得出它的最佳实践对象就是从具有一定通用交互的表格表单类的管理型系统出发,逐渐包含复杂交互的系统应用,并对响应式具有一定的支持。
上图是我们云组件采用的目录结构,用的是 gulp 打包,CSS 里面有 wd 文件夹,主要放了一些第三方的库。更关键主要还是下面,JS 也是一样,wd 是基础库。第五个是最重要的,所有组件都放在这个文件夹下,每个组件包含自己专属的三大件——模板、逻辑、交互、效果和样式。
基于 gulp 的打包
云组件展示站点
云组件的使用人员主要分为三大类,第一类是前端使用者(包括泛前端人员),他们需要学习如何使用,快速用组件(须知道 Angular 的一些基本概念和用法)。第二类是 UI 设计师、项目和产品等,他们需要观察效果是否是适合的,根据库去设计新的此类系统。第三类是游客和其他人员。
关于云组件的新的思考
云组件牵一发会动全身,如果云组件机制运用不好比如一个老的组件更新出了个 bug,会产生很多负面影响,这时该怎么办?
回到云的初始之处我们不难发现,当资源隔绝便不会产生这种影响了(云组件正是利用其反向思维达到的便捷),那么我们相对将云组件资源封闭便可以降低这个影响了。这便是版本控制,不同项目引用相应的云,以达到刚才讲的两者之间的平衡,从而成效最优化。
所以,只有合理控制住才能真正发挥云组件的优势。
多个版本下,我们的开发模式便是就某个项目的云组件升级由该项目决定。因为如果云组件一发版,所有的项目都升级云组件那这个回测的代价就很高了,况且原有的云组件版本也是够之前已经上线的项目的当前版本用了。
实际使用中的问题
云组件的发版有一定的周期性,但实际项目中的需求要快速响应,这时需要采用云组件扩展模块(模式)开发:基于云组件开发本项目的组件级别的模块,对云组件进行扩展或者项目化定制。
四、关于AngularJS的经验与总结
- 模块化:随时准备模块化抽象,这是一个动态的过程。
- 多想想周边的,超过所止的部分 —— 换位思考,方便下游,倒推上游。
- 没有实现不了的效果,只有承受不了的代价。
- 方法有很多,时间允许的话都可以试试。
附录:AnguarJS使用前后对比
旧的写法,以账号为例,省略部分代码,仅对比验证的部分:
<!-- HTML -->
<div>
<label>邮箱:</label>
<input id="email" name="email" type="text">
<span class="reg-result" id="emailImg"></span>
<span class="reg-info" id="emailInfo"></span>
</div>
..
<input name="subLog" value=" 立即加入 " class="btnbtn-primary" id="regSub" type="button">
//JS
functioncheckEmail(input) {
return /^[a-z0-9]+([._\\-]*[a-z0-9])*@([a-z0-9]+[-a-z0-9]*[a-z0-9]+.){1,63}[a-z0-9]+$/.test(input);
}
//初始提示信息
varerrorInfo = new Array(),
rightImg = "<imgsrc="images/right.gif">",
errorImg = "<imgsrc="images/error.png">";
errorInfo[0] = "未填";
errorInfo[1] = "已注册";
errorInfo[2] = "邮箱格式不对";
$("#emailInfo").text(errorInfo[0]);
$("#email").blur(function() {
varthisinput = $(this),
thisValue = thisinput.val() || "";
if (thisValue == "") {
$("#emailImg").html(errorImg);
$("#emailInfo").text(errorInfo[0]);
} else if (!checkEmail(thisValue)) {
//thisinput.addClass("inputError");
$("#emailImg").html(errorImg);
$("#emailInfo").text(errorInfo[2]);
} else {
//should post
if (thisValue === "repeat@getui.com") {
$("#emailImg").html(errorImg);
$("#emailInfo").text(errorInfo[1]);
} else {
$("#emailImg").html(rightImg);
$("#emailInfo").text("可注册");
}
}
});
//提交
$("#regSub").click(function(){
//手动输入框自动检测合法性 - 触发blur事件
$("#email").blur();
varerrorRegNum = 0;
//触发插件性质的输入
//..
//错误信息统计
$(".reg-result").each(function(){
if($(this).find("img").attr("src") == "../images/error.png"){
errorRegNum++;
}
});
if (errorRegNum<= 0) {
$("form#reg").submit();
}
});
采用Angular后的写法如下:
<!-- HTML -->
<formclass="form-horizontal"name="myform">
<divclass="form-group">
<label class="col-md-1">邮箱:</label>
<input class="col-md-4"name="email"placeholder="请输入邮箱完整地址:xxx@getui.com"required=""data-ng-model="vm.user.email"data-ng-pattern="/^[a-z0-9]+([._\\-]*[a-z0-9])*@([a-z0-9]+[-a-z0-9]*[a-z0-9]+.){1,63}[a-z0-9]+$/"data-mywd-email-unique=""type="text">
<spanclass="col-md-2 reg-result">
<imgsrc="../images/error.png"data-ng-if="myform.email.$invalid">
<imgsrc="../images/right.gif"data-ng-if="myform.email.$valid">
</span>
<spanclass="col-md-4 reg-info">
<spandata-ng-if="myform.email.$error.pattern">邮箱地址无效</span>
<spandata-ng-if="myform.email.$error.required">未填</span>
<spandata-ng-if="myform.email.$error.unique">已经被注册了</span>
</span>
</div>
<input name="subLog"value="提交"class="btnbtn-primary"data-ng-disabled="myform.$invalid"data-ng-click="vm.submit()"type="button">
</form>
//js
angular.module("testapp", []).directive("mywdEmailUnique", function() {
return {
require: "ngModel",
link: function(scope, ele, attrs, c) {
scope.$watch(attrs.ngModel, function() {
//post请求,检测是否重复,此处略去,以"repeat@getui.com"为重复的
if (scope.$eval(attrs.ngModel) == "repeat@getui.com") {
c.$setValidity("unique", false);
} else {
c.$setValidity("unique", true);
}
});
}
}
});
了解最新移动开发相关信息和技术,请关注mobilehub公众微信号(ID: mobilehub)。