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

相關文章
相關標籤/搜索