实战问题
假设我熟悉用jQuery开发客户端应用程序,但现在我想开始使用AngularJS。你能描述一下必要的范式转变吗?以下是一些可能有助于您确定答案的问题:
我如何以不同的方式构建和设计客户端 Web 应用程序?最大的区别是什么?
我应该停止做什么/使用什么;我应该开始做什么/使用什么?
是否有任何服务器端注意事项/限制?
我不是在寻找jQuery和之间的详细比较AngularJS。
解决方案
概括,甚至不要使用jQuery。甚至不要包括它。它会阻止你。当你遇到一个你认为你已经知道如何在 jQuery 中解决的问题时,在你使用$. 不知道就问!20 次中有 19 次,最好的方法是不需要 jQuery,而尝试用 jQuery 解决它会为您带来更多的工作。
1.不要设计你的页面,然后用DOM操作改变它
在 jQuery 中,您设计一个页面,然后使其动态化。这是因为 jQuery 是为增强而设计的,并且在这个简单的前提下已经取得了令人难以置信的发展。
但是在 AngularJS 中,您必须从头开始考虑您的架构。与其从思考“我有这块 DOM 并且我想让它做 X”开始,你必须从你想要完成的事情开始,然后设计你的应用程序,最后设计你的视图。
2. 不要用 AngularJS 来扩充 jQuery
同样,不要以为 jQuery 可以执行 X、Y 和 Z 的想法开始,所以我将在模型和控制器的基础上添加 AngularJS。这是真的,当你刚刚起步的,这就是为什么我总是建议新AngularJS开发完全不使用jQuery,至少直到他们习惯做的事情“角路”诱人。
我在这里和邮件列表上看到许多开发人员使用 150 或 200 行代码的 jQuery 插件创建这些精心设计的解决方案,然后他们将这些复杂的回调和$applys的集合粘到 AngularJS 中,这些令人困惑和费解;但他们最终让它发挥作用!问题是,在大多数情况下,jQuery 插件可以用 AngularJS 重写一小部分代码,突然之间一切都变得易于理解和直接了当。
底线是这样的:在解决时,首先“在 AngularJS 中思考”;如果您想不出解决方案,请询问社区;如果毕竟没有简单的解决方案,那么请随时使用 jQuery。但是不要让 jQuery 成为拐杖,否则您将永远无法掌握 AngularJS。
3. 始终从架构的角度思考
首先要知道单页应用程序是应用程序。它们不是网页。因此,除了像客户端开发人员一样思考之外,我们还需要像服务器端开发人员一样思考。我们必须考虑如何将我们的应用程序划分为单独的、可扩展的、可测试的组件。
那么接下来怎么做呢?你如何“在 AngularJS 中思考”?下面是一些与 jQuery 对比的一般原则。
观点是“官方记录”
在 jQuery 中,我们以编程方式更改视图。我们可以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>
在 jQuery 中,在我们的应用程序逻辑中,我们将使用以下内容激活它:
$('.main-menu').dropdownMenu();
当我们只看视图时,并不会立即发现这里有任何功能。对于小型应用程序,这很好。但是对于非平凡的应用程序,事情很快就会变得混乱且难以维护。
然而,在 AngularJS 中,视图是基于视图的功能的官方记录。我们的ul声明看起来像这样:
<ul class="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>Data Received!</li>');
}
});
对于看起来像这样的视图:
<ul class="messages" id="log">
</ul>
除了混合关注点之外,我们也有我之前提到的表示意图的相同问题。但更重要的是,我们必须手动引用和更新 DOM 节点。如果我们想删除一个日志条目,我们也必须针对 DOM 进行编码。除了DOM,我们如何测试逻辑?如果我们想更改演示文稿怎么办?
这有点凌乱,有点脆弱。但是在 AngularJS 中,我们可以这样做:
$http( '/myEndpoint.json' ).then( function ( response ) {
$scope.log.push( { msg: 'Data Received!' } );
});
我们的视图可以是这样的:
<ul class="messages">
<li ng-repeat="entry in log">{{ entry.msg }}</li>
</ul>
但就此而言,我们的观点可能如下所示:
<div class="messages">
<div class="alert" ng-repeat="entry in log">
{{ entry.msg }}
</div>
</div>
现在我们不再使用无序列表,而是使用 Bootstrap 警告框。而且我们永远不必更改控制器代码!但更重要的是,无论日志在何处或如何更新,视图也会发生变化。自动地。整洁的!
虽然我没有在这里展示它,但数据绑定是双向的。因此,这些日志信息,也可以在视图编辑只是这样做:。有很多的欣喜。
不同的模型层
在 jQuery 中,DOM 有点像模型。但是在 AngularJS 中,我们有一个单独的模型层,我们可以以任何我们想要的方式管理它,完全独立于视图。这有助于上述数据绑定,维护关注点分离,并引入了更大的可测试性。其他答案提到了这一点,所以我就留在那里。
关注点分离
以上所有内容都与这个总体主题有关:将您的关注点分开。你的观点是应该发生的事情的官方记录(大部分);您的模型代表您的数据;你有一个服务层来执行可重用的任务;您进行 DOM 操作并使用指令扩充您的视图;然后你用控制器把它们粘在一起。其他答案中也提到了这一点,我唯一要添加的内容与可测试性有关,我将在下面的另一部分中讨论。
依赖注入
帮助我们解决关注点分离的是依赖注入(DI)。如果您来自服务器端语言(从Java到PHP),您可能已经熟悉这个概念,但如果您是来自 jQuery 的客户端,这个概念可能看起来从愚蠢到多余再到时髦. 但事实并非如此。:-)
从广泛的角度来看,DI 意味着您可以非常自由地声明组件,然后从任何其他组件中,只需请求它的一个实例,它就会被授予。您不必知道加载顺序、文件位置或类似内容。威力可能不会立即显现,但我将仅提供一个(常见)示例:测试。
假设在我们的应用程序中,我们需要一个通过REST API实现服务器端存储的服务,并且根据应用程序状态,本地存储也是如此。在我们的控制器上运行测试时,我们不想与服务器通信 -毕竟我们正在测试控制器。我们可以添加一个与原始组件同名的模拟服务,注入器将确保我们的控制器自动获取假的服务——我们的控制器不知道也不需要知道区别。
说到测试...
4. 测试驱动的开发——永远
这实际上是关于架构的第 3 部分的一部分,但它非常重要,我将其作为自己的顶级部分。
在您见过、使用或编写过的所有 jQuery 插件中,有多少带有随附的测试套件?不是很多,因为 jQuery 不太适合。但 AngularJS 是。
在 jQuery 中,测试的唯一方法通常是使用示例/演示页面独立创建组件,我们的测试可以针对该页面执行 DOM 操作。那么我们必须单独开发一个组件,然后将其集成到我们的应用程序中。多么不方便!很多时候,当使用 jQuery 开发时,我们选择迭代而不是测试驱动的开发。谁能怪我们?
但是因为我们有关注点分离,所以我们可以在 AngularJS 中迭代地进行测试驱动开发!例如,假设我们想要一个超级简单的指令在我们的菜单中指示我们当前的路线是什么。我们可以在应用程序的视图中声明我们想要的内容:
<a href="/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我们可以动态更新的类; ngModel允许双向数据绑定;ngShow并以ngHide编程方式显示或隐藏元素;还有更多——包括我们自己编写的那些。换句话说,我们可以在没有DOM 操作的情况下做各种令人敬畏的事情。DOM 操作越少,指令越容易测试,它们越容易设计样式,它们在未来越容易改变,它们的可重用性和可分发性越强。
我看到许多刚接触 AngularJS 的开发人员使用指令作为抛出一堆 jQuery 的地方。换句话说,他们认为“由于我无法在控制器中进行 DOM 操作,我会将这些代码放入指令中”。虽然这当然要好得多,但它通常仍然是错误的。
想想我们在第 3 节中编写的记录器。即使我们将它放在指令中,我们仍然希望以“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 () {
on = !on;
$(element).toggleClass('active', on);
});
}
};
});
这有几个问题:
首先,jQuery 从来都不是必需的。我们在这里做的任何事情都不需要 jQuery!
其次,即使我们的页面上已经有 jQuery,也没有理由在这里使用它;我们可以简单地使用angular.element,当放入一个没有 jQuery 的项目时,我们的组件仍然可以工作。
第三,即使假设jQuery的是需要这种指令工作,jqLite( angular.element)将始终使用jQuery,如果它是装的!所以我们不需要使用$- 我们可以使用angular.element.
第四,与第三个密切相关的是 jqLite 元素不需要被包裹$——element传递给link函数的元素已经是一个 jQuery 元素!
第五,我们在前面的部分提到过,为什么我们将模板内容混合到我们的逻辑中?
这个指令可以重写(即使是非常复杂的情况!)更简单的像这样:
.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;
};
}
};
});
同样,模板内容在模板中,因此您(或您的用户)可以轻松地将其替换为符合任何必要样式的模板,而无需触及逻辑。可重用性 - 繁荣!
而且还有所有其他好处,比如测试——这很容易!无论模板中有什么,指令的内部 API 都不会被触及,因此重构很容易。您可以根据需要随意更改模板,而无需更改指令。无论您更改什么,您的测试仍然通过。
哇!
因此,如果指令不仅仅是类似 jQuery 的函数的集合,那么它们是什么?指令实际上是 HTML 的扩展。如果 HTML 没有做你需要它做的事情,你可以编写一个指令来为你做,然后就像它是 HTML 的一部分一样使用它。
换句话说,如果 AngularJS 没有做一些开箱即用的事情,想想团队将如何完成它以适应ngClick, ngClass, 等。