由于近几年AngularJS的火爆,许多前端工作者渐渐也都把目光移到了如何使用这款框架上,然而作为先前在市场占大多数使用份额的jQuery,如何快速从jQuery的思维转化为Angular的思维是个困扰诸多前端爱好者的问题.故作此文做一点小小建议.
1.不要先设计你的页面,再进行DOM操作
在jQuery中,它让我们很容易的去操作DOM(增加或减少页面内容),不用太多的考虑兼容性问题,所以我们开发的第一步往往会先是设计一个页面,然后再去动态得去改变它的内容.
但在AngularJS中,你必须先在你的心中构建应用,而不是从”我有多少块DOM,我要让这些DOM去做XX”这样的思路去思考,你必须从你的实现去思考,然后开始设计你的应用,最后再设计你的视图,这也是AngularJS数据驱动概念的核心.
2.First think in AngularJS
同样的,不要想着说这里有块功能我可以用jQuery来实现,我只需要把AngularJS放在顶层,然后在控制器中用JQ去操作DOM,这对于那些刚刚接触AngularJS的人是非常有诱惑性的,毕竟这是他们比较熟悉的在JQ里的实现,这也是为什么一些AngularJS大牛建议那些AngularJS新手开发者直到能完全的使用”Angular Way”前,一点都不要用jQuery的原因.
我曾看到过不少开发者喜欢把一些jQuery中几百行的插件代码引入AngularJS的代码中,通过一个$apply
回调在AngularJS中使用,这样的代码将会变得十分混乱可读性差而且复杂.大部分的情景下,jQuery中的插件都是可以在Angular中的重写的,这将会让很多代码看起来更加易于理解更加可读.
总的来说:当你解决问题时,First think in AngularJS
;如果你想不到解决方法,你可以到社区中讨论,到谷歌百度中查找别人的解决方案;如果还是没有比较简单的解决方案,而你又觉得用jQuery来实现更加可行,你可以大胆使用,但千万别让jQuery成为你代码中的支柱,否则你将永远驾驭不了AngularJS;
3.用架构的方式去构建你的应用
AngularJS设计之初就特别适合来做单页应用,首先你要清楚单页应用是应用,而不是单纯的网站,因此你在作为一个前端开发者思考的同时还需加入一点服务端开发人员的考虑,你必须考虑好你的应用将切分成多少单独的部分,每个部分的可拓展性,以及你的测试模块.
到底如何去做?如何去think in AngularJS
?与jQuery对比这里有一些比较常见的原则.
从你自己的’官方模版’去思考
在JQ中,我们动态的改变视图,我们可以用一个下拉菜单的函数来代替这里的ul
,像这样:
<ul class="main-menu">
<li class="active">
<a href="#/home">Home</a>
</li>
<li>
<a href="#/menu1">Menu 1</a>
<ul>
<li><a href="#/sm1">Submenu 1</a></li>
<li><a href="#/sm2">Submenu 2</a></li>
<li><a href="#/sm3">Submenu 3</a></li>
</ul>
</li>
<li>
<a href="#/home">Menu 2</a>
</li>
</ul>
在JQ中,我们在应用中构建一个函数,然后可以想下面这样使用:
$('.main-menu').dropdownMenu();
但你看到视图时,你并不能立刻明显的知道一个DOM元素这里会有什么函数,对于一些小的应用,这是很好的,但对于一些 non-rivial
(语义性强) 应用,这时整个页面可能就会变得开始难以理解并且难以维护.
在AngularJS中,官方推荐的方式是一种 view-based
基于视图的函数,我们的ul
可以声明为如下的方式:
<ul class="main-menu" dropdown-menu>
...
</ul>
这两种方式做了同样的事情,但AngularJS的视图让我没能通过看部分的模块便能知道它所期望的操作.无论什么时候一个开发团队的新成员加入,他都能通过看这些很快理解有一个指令dropdownMenu
绑定在ul
这个元素上,他不需要去依靠直觉猜测答案或者是去看任何代码.视图告诉了他一切.可见这让开发更加清晰.
AngularJS 的新手开发者经常问的一个问题:”我如何找到一个页面中特定种类的元素然后添加指令到上面”,可能很多开发者在被回答说不要这么做的时候很疑惑,但之所以不建议这样做的原因是这像是一种一般jQuery一般AngularJS的方式.这不会让程序良好的运行,视图是你的官方模版,向外用指令来表明自己,你不要再尝试去修改页面的DOM,添加指令到你的视图中实现功能,这将会让整个应用更加清晰.
这也是jQuery与AngularJS思考模式的不同,我们用jQuery写程序可能会反复修改页面上的DOM,而AngularJS则是通过增加指令的方式来让页面视图更加清晰合理.
数据绑定
双向数据绑定是AngularJS最令人振奋的特性之一,这让我们不再需要过多的DOM操作,AngularJS会自动的更新你的视图,因此你不在必须像jQuery中去操作DOM,我们响应事件,然后更新内容,如下:
In jQuery:
$.ajax({
url: '/myEndpoint.json',
success: function ( data, status ) {
$('ul#log').append('<li>Data Received!</li>');
}
});
视图如下:
<ul class="messages" id="log"></ul>
如果此时我们还需要删除其中的某条数据,或是编辑等操作,或是想去修改对应的DOM结构,我们又需要再写一遍代码去操作DOM,我们也将难以从DOM中进行测试,每次我们想改变我们的显示可能都需要对代码做出修改.如何脱离DOM来测试我们的逻辑?又如果我们想改变一些页面表现?
在AngularJS中,我们可以这样做:
<ul class="messages">
<li ng-repeat="entry in log">{{ entry.msg }}</li>
</ul>
如果想修改DOM结构,我们可以这样写:
<div class="messages">
<div class="alert" ng-repeat="entry in log">
{{ entry.msg }}
</div>
</div>
如果这是我们用了Bootstrap 的一些样式,我们不再需要去改变控制器中的代码,而可以直接在html的DOM中直接进行修改,无聊何时我们的log
发生了更改,视图都将会自动的改变!
数据绑定是双向的,这里我没有展示.因此这里的log
可以在视图中实时进行修改,如<input ng-model="entry.msg" />
.
更加清晰的模块分层
在jQuery中,我们可以把DOM看作是一个模块,但在AngularJS中,我们有分开的模块层,我们可以用任何我们想要的方式管理这些模块,完全独立于视图.这帮助我们进行上文所说的数据绑定,维护,分离关注点,已经更加可测试.
分离你的关注点
所有上文提到的其实都有一个共同的主题:让你的关注点分离,你的视图想你的模版那样表现(to do what you supposed);你的model
代表你的数据;你有一个服务层来执行可重复执行的任务;你在你的指令中执行DOM操作丰富你的视图;然后你把这些通过控制器联系在一起.这也是我们经常听到或用到的模块化的思想.关键的还是这种特性让前端测试变得更加方便简单.
依赖注入
依赖注入dependency injection (DI)帮助我们更好的分离模块,如果你用过服务端语言(JAVA/PHP),你大概已经对这个概念很清楚了,但如果你是一个从jQuery转学AngularJS的前端开发者,这个概念可能看起来是愚蠢而且多余的(实则不是).
从一个长远的观点看,依赖注入意味这你可以非常自由的从其他任何组件(components)声明组件,请求组件的一个实例.你不必要去知道加载权限或者文件位置,或者任何像这样的事情.这种力量可能在前端开发上很难明显体现,但在测试中的作用是十分显著的.
再让我们说说应用,我们请求一个从服务端通过REST API返回并且取决与应用状态和本地存储的服务.但我们在控制器中运行测试时,我们不想去与服务交互,毕竟我们仅仅测试控制器.我们可以添加一个与源组件同名的mock
服务,然后注入器会确认我们的控制器是否自动的获得了空壳(mock)-我们的控制器不需要知道两者间(mock和original component)的不同
深入就是测试的话了……
4.测试驱动的开发模式
这个实际上 架构的第三部分,但它的重要性让我把它放到了我最后的讨论上.
纵观所有你用过,看过或写过的jQuery插件,其中有多事是有伴随的测试模块?没有多少,因为测试在jQuery中不怎么好写,但AngularJS则是是否受用的.
在jQuery中,唯一的方法去测试常常是重复创建一个组件独立与简单的页面(sample/demo page)然后我们的测试重复执行DOM操作.因此我们必须分开开发我们的组件,然后再集成到我们的应用中.这明显不是聪明人选择的做法!用jQuery开发需要太多时间,因此我们更倾向于使用测试驱动的开发模式.
在AngularJS中,我们拥有分离的关注点(separation of concerns),我们可以在AngularJS中实现测试驱动的开发模式.举个例子,我们创建一个非常简单的指令来表明我们的菜单现在的路由状态是在哪.我们在应用中声明我们想要的视图:
<a href="/hello" when-active>Hello</a>
Okay,现在我们可以写一个测试给我们这个不存在的when-active
指令:
it( 'should add "active" when the route changes', inject(function() {
var elm = $compile( '<a href="/hello" when-active>Hello</a>' )( $scope );
$location.path('/not-matching');
expect( elm.hasClass('active') ).toBeFalsey();
$location.path( '/hello' );
expect( elm.hasClass('active') ).toBeTruthy();
}));
但我们运行我们的测试,我们可以query它是否失败.只有测试通过我们现在才能创建我们的指令:
.directive( 'whenActive', function ( $location ) {
return {
scope: true,
link: function ( scope, element, attrs ) {
scope.$on( '$routeChangeSuccess', function () {
if ( $location.path() == element.attr( 'href' ) ) {
element.addClass( 'active' );
}
else {
element.removeClass( 'active' );
}
});
}
};
});
我们的测试现在通过了,菜单像我们期望的样子展现.开发即可复用又是测试驱动的,不明觉厉!
5.指令不是打包jQuery代码的天堂
你可能经常听到”只在一个指令中操作DOM”.这是必要的,这样看待指令也没什么错~
让我们深入一点…
一些指令只是修饰一些我们已有的视图,可以考虑用ngClass
,除非一些时候必须进行DOM操作,否则基本上AngularJS中的内置指令足矣.但如果一个带有模版(template)的’工具’指令,它就需要用分离关注点的思想来对待了.这个时候工具的大部分功能代码应该独立地在link或者控制器函数中实现.
AngularJS内置的工具指令让我们构建一些功能非常简单;我们能通过ngClass
方便的更新class;通过ngModel
实现双向数据绑定;通过ngShow
和ngHide
实现显示和隐藏元素;还有很多…包括我们自己写的.换句话说,我们可以做种类繁多的功能而不需要进行DOM才做.越少的DOM操作,指令就越容易去测试,他们的设计越简单,未来的更改也会更加简单.
我见过许多AngularJS开发新手常常使用指令当作编写大量jQuery代码的地方.换句话说,他们就像”既然我不能在控制器中做指令操作,那我可以把代码写在指令中”.当然这比起控制器中写好很多,但显然还是错的.
想想我们第三部分讨论的,尽管我们会把DOM操作放在指令中,但我们依然希望能够以AngularJS的方式去写.有时候压根没必要去做任何DOM操作,也有一些比你相信时候,DOM操作是必要的,但这比你想象中的要少得多!在你要在你的应用的任何地方进行DOM操作时,问你自己是否真的需要.可能还有更好的方式.
这里有个简单的例子展示我经常看到的一种JQ模式.我们需要一个开关的按钮.
.directive( 'myDirective', function () {
return {
template: '<a class="btn">Toggle me!</a>',
link: function ( scope, element, attrs ) {
var on = false;
$(element).click( function () {
on = !on;
$(element).toggleClass('active', on);
});
}
};
});
这里的一些错误如下:
1.jQuery不是必要的,这里做的事情我们并不是一定要用到jQuery.
2.即便在我们的页面中我们已经引入过了jQuery,但没有理由我们要在这里去用它;我们能简单的使用angular.element
,这样我们的组件就算放到了一个没有引入jQuery的项目中依旧可以使用.
3.假设jQuery是对这个指令的运行是必须的,jqLite (angular.element
) 当jQuery加载后也已经使用它了,我们不需要使用$
-我们仅仅使用angular.element
.
4.像第三点提到的那样,这里的jqLite对象不需要用$
包裹*而且link
函数中的element
已经是一个jQuery对象了!
5.就如上面提到的,为何要把模版混入我们的逻辑之中.
这个指令可以像下面这样重写,你会发现简单很多~
.directive( 'myDirective', function () {
return {
scope: true,
template: '<a class="btn" ng-class="{active: on}" ng-click="toggle()">Toggle me!</a>',
link: function ( scope, element, attrs ) {
scope.on = false;
scope.toggle = function () {
scope.on = !scope.on;
};
}
};
});
值得重复一提的是,模版(template
)的职责就是模版,因此你(或者你的用户)可以简单的替换它成为其他必要的样式,而逻辑永远不能被触碰到,而且逻辑也是不可重用的.
这里还有其他很多好处,测试会变得简单!不管模版里有什么,指令内部的API永远不会被触碰,因此重构是简单的.你可以随意改变模版而不会触及指令.不管你对它做了什么修改,你的测试都依旧会通过.
指令如果不是jQuery-like代码的集合,那会是什么?指令实际上是HTML的拓展(extensions of HTML),如果HTML不能做你需要做的一些事情,你写一个指令为你做这件事,然后把它当作HTML中的一部分使用.
另一方面,如果AngularJS遇到不能做的一些事情,可以考虑配合ngClick
,ngClass
,等指令实现
总结
当你开始使用AngularJS,不要在去使用jQuery,也不要去引入jQuery,当你遇到一些你已经用jQuery解决过的问题,在你即将使用$
之前,尝试去思考如果用AngularJS的方式去实现.如果不知道,问!反复问,你最终一定能得到不需要jQuery来实现的最佳解决方法.