如果我有jQuery背景,我怎样“用AngularJS思考”?

本文详细阐述了如何从jQuery背景转向AngularJS开发,包括思维模式的转变、架构设计的变化及测试驱动开发的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这是在Stackoverflow的关于 “jQuery和AngularJS” 的经典问答。

将此文翻译并分享下。


问: by Mark Rajcok

如果我有jQuery背景,我怎样“用AngularJS思考”?

 

即使我对应用jQuery开发客户端很熟悉,但现在我想开始用AngularJS。能不能描述下必要的思维模式的转移?这有几个问题可能对你组织答案有帮助:

·      我怎样不同于jQuery来架构和设计客户端网页应用?最大的区别是什么?

·      我应该停止做/用什么;取而代之的是我应该开始做/用什么?

·      服务器端有没有什么注意事项/限制条件?

我并不是在找一份详细的jQuery和AngularJS的对照表。

 

答: by Josh David Miller

1.不要用DOM操作来转化你设计的页面

在jQuery中,你设计一个页面,然后你将其动态化。这是因为jQuery是为了扩充而设计的,在这个简单的前提下,jQuery已经极速增长。

但是在AngularJS中,你必须从一开始就考虑整个架构。不能再通过思考“我有这块DOM,我要用它做X”来开始设计,取而代之的是你要通过思考你想要完成什么来开始,而后着手设计应用,最后设计视图。

 

2.不要用AngularJS扩充jQuery

同样的,不要以jQuery做X、Y和Z的想法开始,所以我会为模型和控制器将AngularJS加在其顶部。这在你刚刚起步时是十分诱人的,这也是我经常向那些AngularJS的新开发者们建议不要使用任何jQuery的原因,直到他们开始习惯用“AngularJS的方式”编程。

 

我见到过许多(这里的和邮件列表里的)开发者煞费苦心的使用有150或200行代码的jQuery插件创建一些复杂的解决方案,之后他们又用让人困惑和费解的回调函数和$apply把这些代码粘到AngularJS里;但最终他们能让它运行!问题是大多数jQuery插件在一小段代码中可以被AngularJS重写,这样一切就变的豁然开朗、简单明了。

 

最底行是这样的:当制定解决方案时,首先“用AngularJS的方式去思考”,如果你想不到一个解决方案,向社区、论坛请教;如果那里没有简单的解决方案,那么可以随意使用jQuery。但别太依赖jQuery,否则你永远不会精通AngularJS。

 

3.经常从整个架构方面思考

首先要知道单页应用是应用程序。他们不是网页。所以我们不仅要像客户端开发者一样思考,也需要像服务器开发者一样去思考。我们不得不想想如何将我们的应用程序分成独立的、可扩展的、可测试的多个部分。

 

所以你怎样做呢?你怎样“用AngularJS的方式去思考”?这里有些通用的原则,与jQuery形成对比:

 

视图是“正式记录”

在jQuery里,我们通过编程改变视图。我们可以用ul定义一个下拉菜单,如下:

<ulclass="main-menu">
<liclass="active">
<ahref="#/home">Home</a>
</li>
<li>
<ahref="#/menu1">Menu 1</a>
<ul>
<li><ahref="#/sm1">Submenu 1</a></li>
<li><ahref="#/sm2">Submenu 2</a></li>
<li><ahref="#/sm3">Submenu 3</a></li>
</ul>
</li>
<li>
<ahref="#/home">Menu 2</a>
</li>
</ul>

在jQuery中,按我们的程序逻辑,可以通过下面的方式激活它:

$('.main-menu').dropdownMenu();

当我们只看视图时,不是立即呈现出功能。对于小的程序,这是可以的。但对于比较大的应用程序,很快就让人感到困惑和难以维持。

 

AngularJS的想法是,视图是基于视图的功能的正式记录。所以ul表示应该如下:

<ulclass="main-menu"dropdown-menu>
    ...
</ul>

这两个做了同样的事情,但在AngularJS版本中任何人一看模板就知道功能是什么。无论何时一个开发团队的新成员刚进入团队时,她可以看看这个就知道有个叫dropdownMenu的指令在起作用;她不需要凭感觉去猜测对的答案或筛选任何代码。视图告诉我们将要发生的事情。这更加简洁。

 

新AngularJS开发者经常问一个问题:我怎样找到一个特殊种类的所有链接,再给它们增加一条指令。开发者总是大吃一惊,当我们这样回复时:你不用这么做。但你不用这么做的原因是因为这种想法是半jQuery、半AngularJS的,很糟糕。此处的问题是开发者试图在AngularJS的环境里“用jQuery编程”。这永远不会良好运行。视图就是正式的记录。指令外(比下面的更多),你永远永远永远不能改变DOM。指令都应用于视图中,所以目的很清楚:

 

记住:不要设计之后再补救。你必须先架构再设计。

 

数据绑定

到目前为止这是AngularJS最棒的功能之一,并且删掉了很多之前的章节中我提到过的各类型DOM操作需求。AngularJS将自动更新你的视图,所以你不需要那些了!在jQuery中,我们响应事件,之后更新内容。如下:

$.ajax({
url: '/myEndpoint.json',
success: function ( data, status ) {
    $('ul#log').append('<li>DataReceived!</li>');
  }
});

对于看起来如下的视图:

<ulclass="messages"id="log">
</ul>


抛开那些各种担忧,也有我之前提到过的表示目的的同样的问题。但是更重要的是,我们不得不手动引用和更新一个DOM节点。如果我们想要删除一个记录,我们不得不对DOM再次编程。我们怎样测试除了DOM以外的逻辑?如果我们想改变视图演示该做什么?

 

这有点乱,也有点脆弱,但是在AngularJS中,我们可以这么做:

$http('/myEndpoint.json' ).then( function ( response ) {
   $scope.log.push( { msg: 'Data Received!' } );
});

我们的视图看起来是这样的:

<ulclass="messages">
<span style="white-space:pre">	</span><ling-repeat="entryin log">{{entry.msg }}</li>
</ul>

但就此而言,我们的视图看起来可以是这样的:

<divclass="messages">
<divclass="alert"ng-repeat="entry in log">
        {{entry.msg }}
</div>
</div>

而现在取代使用一个无序的列表,我们在使用自举报警框。我们从不用改变控制器代码!但更重要的是,不论记录在哪或怎样更新,视图也将改变。自动地,整洁的!

 

尽管我没有在这展示,数据绑定是双向的。所以这些记录的信息也可以在视图中通过<input ng-model="entry.msg"/>编辑。这是多么令人愉快的事啊。

 

明显的模型层

在jQuery中,DOM有点像模型。但在AngularJS中,我们有一个独立的模型层,可以用我们想用的任何方式来管理它,完全独立于视图。这帮助我们实现以上的数据绑定,维持关注点分离,引入了很好的可测试性。其他回答提到了这点,所以我不多说了。

 

关注点分离

以上的所有都连到这个首要的主题上:保持你的关注点是分离的。你的视图正式记录了应该发生的事情(大多数情况下);你的模型代表了你的数据;你有一个服务层来执行可重复使用的任务;你执行DOM操作,通过指令扩充你的视图;你将所有这些与控制器粘在一起。这也是在其他回答中提到的,也是唯一关于可测试性我想再补充的。下面的章节中将讨论这一点。

 

依赖注入 

帮助我们分离关注点的是依赖注入(DI)。如果你是从服务器端语言转过来的(从Java到PHP),你可能已经熟悉这个概念了,但如果你是从jQuery转过来的客户端开发人员,这个概念像是从愚蠢到多余到赶时髦。但它不是。:-)

 

从一个较为宽泛的观点来看,DI是指你可以非常自由的声明组件,之后你只要从其他组件中请求一个实例,它就会被授权。你不必知道读取顺序,或者文件位置,或者类似这样的事情。这种力量可能不会立即显现,但我将提供一个(共同的)例子:测试。

 

比如在我们的应用程序里,我们需要一个通过REST接口实施服务器端存储的服务,这取决于应用情况和本地存储。当在我们的控制器上运行测试时,我们不想与服务器通讯——毕竟我们是在测试控制器。我们可以增加一个模拟服务,名字与我们原始组件一样,注入器将确保我们的控制器自动得到假的通讯——我们的控制器不知道,也不需要知道其中的区别。

 

说到测试…

 

4.测试驱动开发——一贯如此

 

这的确是第三章架构中的一部分,但是它十分重要,以致于我将它单独作为一章。

在你所有你见过、用过的或编写过的许多jQuery插件中,有多少附带的测试套件?不是很多,因为jQuery并不是很符合,但AngularJS却不一样。

在jQuery中,测试的唯一方式是经常独立创建组件,并附带一个针对我们测试执行的DOM操作的样本/小样页面。所以到时,我们必须单独再开发一个组件,将其集成到我们应用中。真不方便!在开发jQuery时我们花了那么多时间选择了迭代而不是测试驱动开发。谁会责备我们呢?

 

但是因为我们有关注点分离,我们可以在AngularJS中做测试驱动开发迭代!例如,我们想要一个超级简单的指令在菜单中表明当前路径。我们可以在应用视图中声明我们想要的:

<ahref="/hello"when-active>Hello</a>

好了,现在我们可以为不存在的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();
}));

当我们运行测试的时候,我们可以确定这会失败。只有现在我们应按如下编写我们的指令:

.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拥有一整套工具使这方面非常简单;通过ngClass,我们可以动态的更新类;ngBind允许双向数据绑定;ngShowngHide的编程显示或隐藏一个元素;还有很多——包括那些我们自己写的。换句话说,我们可以不用DOM操作就能做很多了不起的事情。DOM越少,测试指令就越简单,设计就越简单,未来的一些变更也就越简单,但可再利用的和可分配的资源就越多。

 

我看到许多刚用AngularJS的开发者使用指令,就像扔进一堆jQuery。换句话说,他们认为“既然我能在控制器里做DOM处理,我也能把那些代码放进指令里”。虽然这当然是更好的,但它仍然经常出错。

 

想一想我们在第三章编写的记录器。虽然我们把它放进了一个指令里,我们仍想要用“Angular的方式”去做。它仍没有用到任何DOM操作!有很多时候DOM是必要的,但这些时候要比你想象的少得多!在你的应用中任何一个位置做DOM操作前,问问自己,这是否的确需要。可能会有更好的办法。

 

这里有个简单的例子,展示了我最经常见的模式。我们想要个可触发的按钮。(注意:这个例子与可用同样地方法解决的更复杂的案例来说,有点做作和啰嗦。)

.directive('myDirective', function () {
return {
template: '<a class="btn">Toggle me!</a>',
link: function ( scope, element, attrs ) {
var on = false;
 
           $(element).click(function () {
if ( on ) {
                   $(element).removeClass('active' );
                }
else {
                   $(element).addClass('active' );
                }
 
on = !on;
            });
        }
    };
});

这里有几件错误的事情:

1.    首先,jQuery从来不是必要的。我们在这里做的事情没有是需要jQuery的!

2.    其次,即使我们已经在页面上有jQuery了,也没有在这里使用的理由;我们可以简单的使用angular.element,当我们做一个不用jQuery的项目时,我们的组件仍会运行。

3.    再次,甚至假设指令要求jQuery运行,如果jqLite(angular.element)被读取,它会总是使用jQuery!所以我们不需要使用$——我们可以只用angular.element

4.    第四,与第三条紧密相关,jqLite元素不需要包在$中——element传递给link功能已经成为了一个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;
            };
        }
    };
});

重申一遍,模板的内容都在模板里,所以你(或者你的用户)可以简单的为符合任何设计需要的将其换出来,绝不会接触到逻辑部分。可重用性——砰!

 

还有很多其他的优点,比如测试——很简单!不论模板里有什么,永远不会接触到指令的内部接口,所以重构非常简单。你不用接触指令,便可随意改变模板。不论你改变什么,你的测试都将通过。

哈哈!

所以如果指令不是jQuery集合——如功能,他们是什么?指令实际上是HTML的扩展。如果HTML没做你需要它做的,你可以写个指令命令他,将其看做HTML的一部分运用他。

换句话说,如果AngularJS没有做什么创造性的东西,考虑下团队通过ngClickngClass等完成它。

 

总结 

不要用jQuery。甚至不要包含它。它会托你后腿。当你遇到问题,并且你认为你知道怎样用jQuery解决它的时候,在你使用 $ 前,试着想象在AngularJS的界限内怎样实现它。如果你不知道,就问问!20次里有19次,最好的办法都不需要jQuery,如果用jQuery解决它,结果是你要做更多的工作。


点击原文链接



此文在CC-By-SA 3.0许可证下使用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值