StackOverFlow精彩问答赏析:有jQuery背景的开发者如何创建起AngularJS的思惟

【编辑注】本文来自StackOverFlow上How do I “think in AngularJS” if I have a jQuery background?一题中得票最高的回答。该回答得票超过3000次,回答者Josh David Miller是活跃于开源社区的开发者,也是Emergenesis公司的联合创始人。该答案最初由数云架构师韩铮翻译并发布在本身的博客上,在征得Josh赞成后由韩铮本人推荐给 InfoQ进行分享,并在通过InfoQ社区编辑崔康审校后发布在此。php

1. 不要先设计页面,而后再使用DOM操做来改变它的展示

在jQuery中,你一般会设计一个页面,而后再给它动态效果。这是由于jQuery的设计就是为了扩充DOM并在这个简单的前提下疯狂的生长的。html

可是在AngularJS里,必须从头开始就在头脑中思考架构。必须从你想要完成的功能开始,而后设计应用程序,最后来设计视图,而非“我有这么一个DOM片断,我想让他能够实现XXX效果”。java

2. 不要用AngularJS来增强jQuery

相似的,不要以这样的思惟开始:用jQuery来作X,Y和Z,而后只须要把AngularJS的models和controllers加在这上面。这在刚开始的时候显得很是诱人,这也是为何我老是建议AngularJS的新手彻底不使用jQuery,至少不要在习惯使用“Angular Way”开发以前这么作。jquery

我在邮件列表里看到不少开发者使用150或200行代码的jQuery插件创造出这些复杂的解决方案,而后使用一堆callback函数以及$apply把它粘合到AngularJS里,看起来复杂难懂;可是他们最终仍是把它搞定了!问题是在大多数状况下这些jQuery插件可使用不多的AngularJS代码重写,并且全部的一切都很简单直接容易理解。git

这里的底线是:当你选择解决方案时,首先“think in AngularJS”;若是想不出一个解决方案,去社区求助;若是仍是没有简单的解决方案,再考虑使用jQuery。可是不要让jQuery成为你的拐杖,致使你永远没法真正掌握AngularJS。angularjs

3. 老是以架构的角度思考

首先要知道Single-page应用是应用,不是网页。因此咱们除了像一个客户端开发者般思考外,还须要像一个服务器端开发者同样思考。咱们必须考虑如何把咱们的应用分割成独立的,可扩展且可测试的组件。github

那么如何作到呢?如何“think in AngularJS”?这里有一些基本原则,对比jQuery。ajax

视图是“Official Record”

在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里,咱们会在应用逻辑里这样启用这个下拉菜单:json

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

当咱们只关注视图,这里不会当即明显的体现出任何(业务)功能。对于小型应用,这没什么不妥。可是在规模较大的应用中,事情就会变得难以理解且难以维护。

而在AngularJS里,视图是基于视图的功能。ul声明就会像这样:

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

这两种方式作了一样的东西,可是在AngularJS的版本里任何人看到这个模版均可以知道将会发生什么事。不论什么时候一个新成员加入开发团队,他看到这个就会知道有一个叫作dropdownMenu的directive做用在这个标签上;他不须要靠直觉去猜想代码的功能或者去看任何代码。视图自己告诉咱们会发生什么事。清晰多了。

首次接触AngularJS的开发者一般会问这样一个问题:如何找到全部的某类元素而后给它们加上一个directive。但当咱们告诉他:别这么作时,他总会显得很是的惊愕。而不这么作的缘由是这是一种半jQuery半AngularJS的方式,这么作很差。这里的问题在于开发者尝试在 AngularJS的环境里“do jQuery”。这么作总会有一些问题。视图是official record(译者注:做者可能想表达视图是一等公民)。在一个directive外,毫不要改变DOM。全部的directive都应用在试图上,意图很是清晰。

记住:不要设计,而后写标签。你须要架构,而后设计。

数据绑定

这是到如今为止最酷的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节点。若是咱们想要删除一个log条目,也须要针对DOM编码。那么如何脱离DOM来测试这个逻辑?若是想要改变展示形式怎么办?

这有一点凌乱琐碎。可是在AngularJS里,能够这样来实现:

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

视图看起来是这个样子的:

<ul class="messages"> <li ng-repeat="entry in log"></li> </ul>

可是其实还能够这样来作:

<div class="messages"> <div class="alert" ng-repeat="entry in log">  </div> </div>

如今若是咱们想使用Bootstrap的alert boxes,而不是一个无序列表,根本不须要改变任何的controller代码!更重要的是,不论log在何处或如何被更新,视图便会随之更新。自动的。巧妙!

尽管我没有在这里展现,数据绑定实际上是双向的。因此这些log信息在视图里也能够是可编辑的。只须要这么作:

<input ng-model="entry.msg" />

。简单快乐。

清晰的模型(Model)层

在jQuery里,DOM在必定程度上扮演了模型的角色。但在AngularJS中,咱们有一个独立的模型层能够灵活的管理。彻底与视图独立。这有助于上述的数据绑定,维护了关注点的分离(独立的考虑视图和模型),而且引入了更好的可测性。后面还会提到这点。

关注点分离

上面全部的内容都与这个愿景相关:保持你的关注点分离。视图负责展示将要发生的事情;模型表现数据;有一个service层来实现可复用的任务;在 directive里面进行DOM操做和扩展;使用controller来把上面的东西粘合起来。这在其余的答案里也有叙述,我在这里只增长关于可测试性的内容,在后面的一个段落里详述。

依赖注入

依赖注入帮咱们实现了关注点分离。若是你来自一个服务器语言(java或php),可能对这个概念已经很是熟悉,可是若是你是一个来自jQuery的客户端开发者,这个概念可能看起来有点傻而多余。但其实不是的。。。

大致来说,DI意味着能够很是自由的声明组件,而后在另外一个组件里,只须要请求一个该组件的实例,就能够获得它。不须要知道(关心)加载顺序,或者文件位置,或相似的事情。这种强大可能不会马上显现,可是我只提供一个(常见。。)的例子:测试。

就说在你的应用里,咱们须要一个服务经过REST API来实现服务器端存储,而且根据不一样的应用状态,也有可能使用(客户端)本地存储。当咱们运行controller的测试时,不但愿必须和服务器交互 —— 毕竟是在测试controller逻辑。咱们能够只添加一个与原本使用的service同名的mock service,injector会确保controller自动获得假的那个service —— controller不会也不须要知道有什么不一样。

提及测试……

4. 老是 —— 测试驱动开发

这实际上是关于架构的第3节。可是它过重要了,因此我把它单独拿出来做为一个顶级段落。

在全部那些你见过,用过或写过的jQuery插件中,有多少是有测试集的?很少,由于jQuery经不起测试的考验。可是AngularJS能够。

在jQuery中,惟一的测试方式一般是独立地建立附带sample/demo页面的组件,而后咱们的测试在这个页面上作DOM操做。因此咱们必须独立的开发一个组件,而后集成到应用里。多不方便!在使用jQuery开发时,太多的时间,咱们挑选迭代而非测试驱动开发。谁又能责怪咱们呢?

可是由于有了关注点分离,咱们能够在AngularJS中迭代地作测试驱动开发!例如,想要一个超级简单的directive来展示咱们的当前路径。能够在视图里声明:

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

OK,如今能够写一个测试:

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了:

.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');
                }
            });
        }
    };
});

测试如今经过了,而后咱们的menu按照请求的方式执行。开发过程既是迭代的也是测试驱动的。太酷了。

5. 概念上,Directives并非打包的jQuery

你常常会听到“只在directive里作DOM操做”。这是必需的。请给它应有的尊重!

但让咱们再深刻一点……

一些directive仅仅装饰了视图中已经存在的东西(想一想ngClass)而且所以有时候仅仅直接作完DOM操做而后就完事了。可是若是一个 directive像一个“widget”而且有一个模版,那么它也要作到关注点分离。也就是说,模版自己也应该很大程度上与其link和 controller实现保持独立。

AngularJS拥有一整套工具使这个过程很是简单;有了ngClass咱们能够动态地更新class;ngBind使得咱们能够作双向数据绑定。ngShow和ngHide可编程地展现和隐藏一个元素;以及更多地 —— 包括那些咱们本身写的。换句话说,咱们能够作到任何DOM操做能实现的特性。DOM操做越少,directive就越容易测试,也越容易给它们添加样式,在将来也越容易拥抱变化,而且更加的可复用和发布。

我见过不少AngularJS新手,把一堆jQuery扔到directive里。换句话说,他们认为“由于不能在controller里作DOM操做,就把那些代码弄到directive里好了”。虽然这么作确实好一些,可是依然是错误的。

回想一下咱们在第3节里写的那个logger。即便要把它放在一个directive里,咱们依然但愿用“Angular Way”来作。它依然没有任何DOM操做!有不少时候DOM操做是必要的,但其实比你想的要少得多!在应用里的任何地方作DOM操做以前,问问你本身是否是真的须要这么作。有可能有更好的方式。

这里有一个示例,展现出了我见过最多的一种模式。咱们想作一个能够toggle的按钮。(注意:这个例子有一点牵强、啰嗦,这是为了表达出使用一样方式处理问题的更复杂的状况。)

.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;
            });
        }
    };
});

这里有一些错误的地方。首先,jQeury根本不必出现。咱们在这里作的事情都根本用不着jQuery!其次,即便已经将jQuery用在了页面上,也没有理由用在这里。第三,即便假设这个directive依赖jQuery来工做,jqLite(angular.element)在加载后总会使用jQuery!因此咱们不必使用$ —— 用angular.element就够了。第四,和第三条紧密关联,jqLite元素不须要被$封装 —— 传到link里的元素原本就会是一个jQuery元素!第五,咱们在前面段落中说过,为何要把模版的东西混到逻辑里?

这个directive能够(即便是更复杂的状况下!)写得更简单:

.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;
            };
        }
    };
});

再一次地,模版就在模版里,当有样式需求时,你(或你的用户)能够轻松的换掉它,不用去碰逻辑。重用性 —— boom!

固然还有其余的好处,像测试 —— 很简单!不论模版中有什么,directive的内部API历来不会被碰到,因此重构也很容易。能够不碰directive就作到任意改变模版。不论你怎么改,测试老是经过的。

因此若是directive不只仅是一组相似jQuery的函数,那他们是什么?Directive实际是HTML的扩展。若是HTML没有作你须要它作的事情,你就写一个directive来实现,而后就像使用HTML同样使用它。

换句话说,若是AngularJS库没有作的一些事情,想一想开发团队会如何完成它来配合ngClick,ngClass等。

总结

不要用jQuery。连include也不要。它会让你停滞不前。若是遇到一个你认为已经知道如何使用jQuery来解决的问题,在使用$以前,试试想一想如何在AngularJS的限制下解决它。若是你不知道,问!20次中的19次,最好的方式不须要jQuery。若是尝试使用jQuery会增长你的工做量。

这是我目前最长的Stack Overflow回答。事实上,这个答案太长了,我都要填一个Captcha了。可是就如我常说的:能说多时候说的少其实就是懒。

但愿这个答案对你有用。

原文英文地址: http://stackoverflow.com/questions/14994391/how-do-i-think-in-angularjs-if-i-have-a-jquery-background

相关文章
相关标签/搜索