JavaScript 之 Angular 設計風格指南

簡要說明:本文主要摘錄于 Angular 官網中 JavaScript 的設計風格指南。本風格指南介紹了提倡的約定,更重要的是,解釋了為什么。個人覺得對于 JavaScript 開發時的規范約束、以及編程風格等講解得十分透徹。不愧為大公司出品,對于學習乃至項目的研發,都大有裨益。

一、風格詞匯

每個指導原則都會描述好的或者壞的做法,所有指導原則風格一致。

指導原則中使用的詞匯表明推薦的程度。

  1. 堅持意味著總是應該遵循的約定。 總是可能有點太強了。應該總是遵循的指導原則非常少。 但是,只有遇到非常不尋常的情況才能打破堅持的原則。
  2. 考慮標志著通常應該遵循的指導原則。
    如果能完全理解指導原則背后的含義,并且很好的理由背離它,那就可以那么做。但是請保持一致。
  3. 避免標志著我們決不應該做的事。

二、文件結構約定

在一些代碼例子中,有的文件有一個或多個相似名字的伴隨文件。(例如 hero.component.tshero.component.html)。

本指南將會使用像hero.component.ts|html|css|spec的簡寫來表示上面描述的多個文件,目的是保持本指南的簡潔性,增加描述文件結構時的可讀性。

三、單一職責

所有組件、服務和其它符號都要遵循單一職責原則。 這會使應用程序更干凈,易于閱讀和維護,提高可測試性。

3.1 單一法則
  1. 堅持每個文件只定義一樣東西(例如服務或組件)。
  2. 考慮把文件大小限制在 400 行代碼以內。

單組件文件非常容易閱讀、維護,并能防止在版本控制系統里與團隊沖突。

單組件文件可以防止一些隱蔽的程序缺陷,當把多個組件合寫在同一個文件中時,可能造成共享變量、創建意外的閉包,或者與依賴之間產生意外耦合等情況。

單獨的組件通常是該文件默認的導出,可以用路由器實現按需加載。

最關鍵的是,可以增強代碼可重用性和閱讀性,減少出錯的可能性。

隨著應用程序的成長,本法則會變得越來越重要。

3.2 簡單函數
  1. 堅持定義簡單函數
  2. 考慮限制在 75 行之內。

簡單函數更易于測試,特別是當它們只做一件事,只為一個目的服務時。
簡單函數促進代碼重用,更易于閱讀,更易于維護。
簡單函數可避免易在大函數中產生的隱蔽性錯誤,例如與外界共享變量、創建意外的閉包或與依賴之間產生意外耦合等。

四、命名

命名約定對可維護性和可讀性非常重要。本指南為文件名和符號名推薦了一套命名約定。

4.1 總體命名指導原則
  1. 堅持所有符號使用一致的命名規則。
  2. 堅持遵循同一個模式來描述符號的特性和類型。推薦的模式為feature.type.ts

為何?命名約定提供了一致的方式來查找內容,讓我們一眼就能鎖定。 項目的一致性是至關重要的。團隊內的一致性也很重要。整個公司的一致性會提供驚人的效率。

命名約定幫助我們更快得找到不在手頭的代碼,更容易理解它。

目錄名和文件名應該清楚的傳遞它們的意圖。 例如,app/heroes/hero-list.component.ts包含了一個用來管理英雄列表的組件。

4.2 使用點和橫杠來分隔文件名
  1. 堅持在描述性名字中,用橫杠來分隔單詞。
  2. 堅持使用來分隔描述性名字和類型。
  3. 堅持遵循先描述組件特性,再描述它的類型的模式,對所有組件使用一致的類型命名規則。推薦的模式為feature.type.ts
  4. 堅持使用慣用的后綴來描述類型,包括*.service*.component*.pipe*.module*.directive。 必要時可以創建更多類型名,但必須注意,不要創建太多。

為何?類型名字提供一致的方式來快速的識別文件中有什么。

為何? 利用編輯器或者 IDE 的模糊搜索功能,可以很容易地找到特定文件。

為何? 像.service這樣的沒有簡寫過的類型名字,描述清楚,毫不含糊。 像.srv, .svc, 和 .serv這樣的簡寫可能令人困惑。

為何?為自動化任務提供模式匹配。

4.3 符號名與文件名
  1. 堅持為所有東西使用一致的命名約定,以它們所代表的東西命名。
  2. 堅持使用大寫駝峰命名法來命名類。符號名匹配它所在的文件名。
  3. 堅持在符號名后面追加約定的類型后綴(例如Component、Directive、Module、Pipe、Service)。
  4. 堅持在文件名后面追加約定的類型后綴(例如.component.ts、.directive.ts、.module.ts、.pipe.ts、.service.ts)。

為何?提供一致的方式來快速標識和引用資產。

為何?大駝峰命名法用于標識可以通過構造函數實例化的對象。

符號名
文件名

export class AppComponent { }
app.component.ts

export class HeroesComponent { }
heroes.component.ts

export class HeroListComponent { }
hero-list.component.ts

export class ValidationDirective { }
validation.directive.ts

export class AppModule
app.module.ts

export class InitCapsPipe implements PipeTransform { }
init-caps.pipe.ts

export class UserProfileService { }
user-profile.service.ts
4.4 服務名
  1. 堅持使用一致的規則命名服務,以它們的特性來命名。
  2. 堅持使用大寫駝峰命名法命名服務。
  3. 堅持添加Service后綴,當不清楚它們是什么時(例如當它們是名詞時)。

為何?提供一致的方式來快速識別和引用服務。

為何?像Logger這樣的清楚的服務名不需要后綴。

為何?像Credit這樣的,服務名是名詞,需要一個后綴。當不能明顯分辨它是服務還是其它東西時,應該添加后綴。

符號名
文件名

export class HeroDataService { }
hero-data.service.ts

export class CreditService { }
credit.service.ts

export class Logger { }
logger.service.ts
4.5 引導
  1. 堅持把應用的引導程序和平臺相關的邏輯放到名為main.ts的文件里。
  2. 堅持在引導邏輯中包含錯誤處理代碼
  3. 避免把應用邏輯放在main.ts中,而應放在組件或服務里。

為何?應用的啟動邏輯遵循一致的約定。

為何?這是從其它技術平臺借鑒的常用約定。

// main.ts
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule }              from './app/app.module';
platformBrowserDynamic().bootstrapModule(AppModule)
  .then(success => console.log(`Bootstrap success`))
  .catch(err => console.error(err));
4.6 指令選擇器
  1. 堅持使用小駝峰命名法來命名指令的選擇器。

為何?保持指令中定義的屬性名與綁定的視圖 HTML 屬性名字一致。

為何?Angular HTML 解析器是大小寫敏感的,它識別小寫駝峰寫法。

4.7 單元測試文件名
  1. 堅持測試規格文件名與被測試組件文件名相同。
  2. 堅持測試規格文件名添加.spec后綴。

為何?提供一致的方式來快速識別測試。

為何?提供一個與 karma 或者其它測試運行器相配的命名模式。

五、代碼約定

堅持一致的編程、命名和空格的約定。

5.1 類
  1. 堅持使用大寫駝峰命名法來命名類。

為何?遵循類命名傳統約定。

為何?類可以被實例化和構造實例。根據約定,用大寫駝峰命名法來標識可構造的東西。

5.2 常量
  1. 堅持用const聲明變量,除非它們的值在應用的生命周期內會發生變化。

為何?告訴讀者這個值是不可變的。

為何? TypeScript 會要求在聲明時立即初始化,并阻止再次賦值,以確保達成我們的意圖。

  1. 考慮 把常量名拼寫為小駝峰格式。

為何?小駝峰變量名 (heroRoutes) 比傳統的大寫蛇形命名法 (HERO_ROUTES) 更容易閱讀和理解。

為何? 把常量命名為大寫蛇形命名法的傳統源于現代 IDE 出現之前, 以便閱讀時可以快速發現那些const定義。 TypeScript 本身就能夠防止意外賦值。

  1. 堅持容許現存的const常量沿用大寫蛇形命名法。

為何?傳統的大寫蛇形命名法仍然很流行、很普遍,特別是在第三方模塊中。 修改它們沒多大價值,還會有破壞現有代碼和文檔的風險。

// app/shared/data.service.ts
export const mockHeroes   = ['Sam', 'Jill']; // prefer
export const heroesUrl    = 'api/heroes';    // prefer
export const VILLAINS_URL = 'api/villains';  // tolerate
5.3 接口
  1. 堅持使用大寫駝峰命名法來命名接口。
  2. 考慮不要在接口名字前面加I前綴。
  3. 考慮用類代替接口。

為何?TypeScript 指導原則不建議使用 “I” 前綴。

為何?單獨一個類的代碼量小于類 + 接口。

為何?類可以作為接口使用(只是用implements代替extends而已)。

為何?在 Angular 依賴注入系統中,接口類可以作為服務提供商的查找令牌。

// app/shared/hero-collector.service.ts
/* avoid */
import { Injectable } from '@angular/core';
import { IHero } from './hero.model.avoid';
@Injectable()
export class HeroCollectorService {
  hero: IHero;
  constructor() { }
}

// app/shared/hero-collector.service.ts
import { Injectable } from '@angular/core';
import { Hero } from './hero.model';
@Injectable()
export class HeroCollectorService {
  hero: Hero;
  constructor() { }
}
5.4 屬性和方法
  1. 堅持使用 小寫駝峰命名法 來命名屬性和方法。
  2. 避免為私有屬性和方法添加_前綴。

為何?遵循傳統屬性和方法的命名約定。

為何? JavaScript 不支持真正的私有屬性和方法。

為何? TypeScript 工具讓識別私有或公有屬性和方法變得很簡單。

5.5 導入語句中的空行
  1. 堅持在第三方導入和應用導入之間留一個空行。
  2. 考慮按模塊名字的字母順排列導入行。
  3. 考慮在解構表達式中按字母順序排列導入的東西。

為何?空行可以讓閱讀和定位本地導入更加容易。

為何?按字母順序排列可以讓閱讀和定位本地導入更加容易。

// app/heroes/shared/hero.service.ts
/* avoid */
import { SpinnerService, ExceptionService, ToastService } from '../../core';
import { Http, Response } from '@angular/http';
import { Injectable } from '@angular/core';
import { Hero } from './hero.model';

// app/heroes/shared/hero.service.ts
import { Injectable } from '@angular/core';
import { Http, Response } from '@angular/http';

import { Hero } from './hero.model';
import { ExceptionService, SpinnerService, ToastService } from '../../core';

六、應用程序結構與 Angular 模塊

準備一個近期實施方案和一個長期的愿景。從零開始,但要考慮應用程序接下來的路往哪兒走。

所有應用程序的源代碼都放到名叫app的目錄里。 所有特性區都在自己的文件夾中,帶有它們自己的 Angular 模塊。

所有內容都遵循每個文件一個特性的原則。每個組件、服務和管道都在自己的文件里。 所有第三方程序包保存到其它目錄里,不是app目錄。 你不會修改它們,所以不希望它們弄亂我們的應用程序。 使用本指南介紹的文件命名約定。

6.1 LIFT
  1. 堅持組織應用的結構,達到這些目的:快速定位 (Locate) 代碼、一眼識別 (Identify) 代碼、 盡量保持扁平結構 (Flattest) 和嘗試 (Try) 遵循DRY (Do Not Repeat Yourself, 不重復自己) 原則。
  2. 堅持四項基本原則定義文件結構,上面的原則是按重要順序排列的。

為何?LIFT 提供了一致的結構,它具有擴展性強、模塊化的特性。因為容易快速鎖定代碼,提高了開發者的效率。

另外,檢查應用結構是否合理的方法是問問自己:我們能快速打開與此特性有關的所有文件并開始工作嗎?

6.2 定位

堅持直觀、簡單和快速地定位代碼。

為何? 要想高效的工作,就必須能迅速找到文件,特別是當不知道(或不記得)文件名時。 把相關的文件一起放在一個直觀的位置可以節省時間。 富有描述性的目錄結構會讓你和后面的維護者眼前一亮。

6.3 識別
  1. 堅持命名文件到這個程度:看到名字立刻知道它包含了什么,代表了什么。
  2. 堅持文件名要具有說明性,確保文件中只包含一個組件。
  3. 避免創建包含多個組件、服務或者混合體的文件。

為何?花費更少的時間來查找和琢磨代碼,就會變得更有效率。 較長的文件名遠勝于較短卻容易混淆的縮寫名。

當你有 一組小型、緊密相關的特性 時,違反一物一文件的規則可能會更好, 這種情況下單一文件可能會比多個文件更容易發現和理解。注意這個例外。

6.4 扁平
  1. 堅持盡可能保持扁平的目錄結構。
  2. 考慮當同一目錄下達到 7 個或更多個文件時創建子目錄。
  3. 考慮配置 IDE,以隱藏無關的文件,例如生成出來的.js文件和.js.map文件等。

為何?沒人想要在超過七層的目錄中查找文件。扁平的結構有利于搜索。

另一方面,心理學家們相信, 當關注的事物超過 9 個時,人類就會開始感到吃力。 所以,當一個文件夾中的文件有 10 個或更多個文件時,可能就是創建子目錄的時候了。

還是根據你自己的舒適度而定吧。 除非創建新文件夾能有顯著的價值,否則盡量使用扁平結構。

6.5 T-DRY (嘗試不重復自己)
  1. 堅持 DRY(Don't Repeat Yourself,不重復自己)。
  2. 避免過度 DRY,以致犧牲了閱讀性。

為何?雖然 DRY 很重要,但如果要以犧牲 LIFT 的其它原則為代價,那就不值得了。 這也就是為什么它被稱為 T-DRY。 例如,把組件命名為hero-view.component.html是多余的,因為組件顯然就是一個視圖 (view)。 但如果它不那么顯著,或不符合常規,就把它寫出來。
???上面例子,與 DRY 有什么關系--jiangxtx --2017-2-11

6.6 總體結構指導原則
  1. 堅持從零開始,但要考慮應用程序接下來的路往哪兒走。
  2. 堅持有一個近期實施方案和一個長期的愿景。
  3. 堅持把所有源代碼都放到名為app的目錄里。
  4. 堅持如果組件具有多個伴隨文件 (.ts、.html、.css和.spec),就為它創建一個文件夾。

為何?在早期階段能夠幫助保持應用的結構小巧且易于維護,這樣當應用增長時就容易進化了。

為何?組件通常有四個文件 (*.html、 *.css、 *.ts*.spec.ts),它們很容易把一個目錄弄亂。

把組件放在專用目錄中的方式廣受歡迎,對于小型應用,還可以保持組件扁平化(而不是放在專用目錄中)。 這樣會把四個文件放在現有目錄中,也會減少目錄的嵌套。無論你如何選擇,請保持一致。

6.7 按特性組織的目錄結構
  1. 堅持根據特性區命名目錄。

為何?開發人員可以快速定位代碼,掃一眼就能知道每個文件代表什么,目錄盡可能保持扁平,既沒有重復也沒有多余的名字。

為何? LIFT 原則中包含了所有這些。

為何?遵循 LIFT 原則精心組織內容,避免應用變得雜亂無章。

為何?當有很多文件時(例如 10 個以上),在專用目錄型結構中定位它們會比在扁平結構中更容易。

2.堅持為每個特性區創建一個 Angular 模塊。

為何? Angular 模塊使延遲加載可路由的特性變得更容易。

為何?Angular 模塊隔離、測試和復用特性更容易。

6.8 應用的根模塊

堅持在應用的根目錄創建一個 Angular 模塊(例如/app)。

為何?每個應用都至少需要一個根 Angular 模塊。

考慮把根模塊命名為app.module.ts。

為何?能讓定位和識別根模塊變得更容易。

6.9 特性模塊
  1. 堅持為應用中每個明顯的特性創建一個 Angular 模塊。
  2. 堅持把特性模塊放在與特性區同名的目錄中(例如app/heroes)。
  3. 堅持特性模塊的文件名應該能反映出特性區的名字和目錄(例如app/heroes/heroes.module.ts)。
  4. 堅持特性模塊的符號名應該能反映出特性區、目錄和文件名(例如在app/heroes/heroes.module.ts中定義HeroesModule)。

為何?特性模塊可以對其它模塊暴露或隱藏自己的實現。

為何?特性模塊標記出組成該特性分區的相關組件集合。

為何?方便路由到特性模塊 —— 無論是用主動加載還是惰性加載的方式。

為何?特性模塊在特定的功能和其它應用特性之間定義了清晰的邊界。

為何?特性模塊幫助澄清開發職責,以便于把這些職責指派給不同的項目組。

為何?特性模塊易于隔離,以便測試。

6.10 核心特性模塊
  1. 堅持把那些“只用一次”的類收集到CoreModule中,并對外隱藏它們的實現細節。簡化的AppModule會導入CoreModule,并且把它作為整個應用的總指揮。
  2. 堅持在core目錄下創建一個名叫CoreModule的特性模塊(例如在app/core/core.module.ts中定義CoreModule)。
  3. 堅持把一個要共享給整個應用的單例服務放進CoreModule中(例如ExceptionServiceLoggerService)。
  4. 堅持導入CoreModule中的資產所需要的全部模塊(例如CommonModuleFormsModule)。

為何? CoreModule提供了一個或多個單例服務。Angular 使用應用的根注入器注冊這些服務提供商,讓每個服務的這個單例對象對所有需要它們的組件都是可用的,而不用管該組件是通過主動加載還是惰性加載的方式加載的。

為何?CoreModule將包含一些單例服務。而如果惰性加載模塊導入這些服務,它就會得到一個新實例,而不是所期望的全應用級單例。

  1. 堅持把應用級、只用一次的組件收集到CoreModule中。 只在應用啟動時從AppModule中導入它一次,以后再也不要導入它(例如NavComponentSpinnerComponent)。

為何?真實世界中的應用會有很多只用一次的組件(例如 加載動畫、消息浮層、模態框 等),它們只會在AppComponent的模板中出現。 不會在其它地方導入它們,所以沒有共享的價值。 然而它們又太大了,放在根目錄中就會顯得亂七八糟的。

  1. 避免在AppModule之外的任何地方導入CoreModule

為何?如果惰性加載的特性模塊直接導入CoreModule,就會創建它自己的服務副本,并導致意料之外的后果。

為何?主動加載的特性模塊已經準備好了訪問AppModule的注入器,因此也能取得CoreModule中的服務。

堅持從CoreModule中導出AppModule需導入的所有符號,使它們在所有特性模塊中可用。

為何?CoreModule的存在就讓常用的單例服務在所有其它模塊中可用。

為何?你希望整個應用都使用這個單例服務。 你不希望每個模塊都有這個單例服務的單獨的實例。 然而,如果CoreModule中提供了一個服務,就可能偶爾導致這種后果。

src
app
core
core.module.ts
logger.service.ts|spec.ts
nav
nav.component.ts|html|css|spec.ts
spinner
spinner.component.ts|html|css|spec.ts
spinner.service.ts|spec.ts
app.component.ts|html|css|spec.ts
app.module.ts
app-routing.module.ts
main.ts
index.html
...
app/app.module.ts app/core/core.module.ts app/core/logger.service.ts app/core/nav/nav.component.ts app/core/nav/nav.component.html app/core/spinner/spinner.component.ts app/core/spinner/spinner.component.html app/core/spinner/spinner.service.ts
COPY CODE
import { NgModule }      from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent }   from './app.component';
import { HeroesComponent } from './heroes/heroes.component';
import { CoreModule }    from './core/core.module';
@NgModule({
  imports: [
    BrowserModule,
    CoreModule,
  ],
  declarations: [
    AppComponent,
    HeroesComponent
  ],
  exports: [ AppComponent ],
  entryComponents: [ AppComponent ]
})
export class AppModule {}

AppModule變得更小了,因為很多應用根部的類都被移到了其它模塊中。 AppModule變得穩定了,因為你將會往其它模塊中添加特性組件和服務提供商,而不是這個AppModule。 AppModule把工作委托給了導入的模塊,而不是親力親為。 AppModule聚焦在它自己的主要任務上:作為整個應用的總指揮。

6.11 惰性加載的目錄
  1. 某些邊界清晰的應用特性或工作流可以做成惰性加載或按需加載的,而不用總是隨著應用啟動。
  2. 堅持把惰性加載特性下的內容放進惰性加載目錄中。 典型的惰性加載目錄包含路由組件及其子組件以及與它們有關的那些資產和模塊。

為何?這種目錄讓標識和隔離這些特性內容變得更輕松。

七、組件

7.1 組件選擇器命名

堅持使用中線- (dashed) 命名法或烤串 (kebab) 命名法來命名組件中的元素選擇器。

為何?保持元素命名與自定義元素命名規范一致。

7.2 把組件當做元素

堅持通過選擇器把組件定義為元素。

為何?組件有很多包含 HTML 以及可選 Angular 模板語法的模板。它們多數都與把內容放進頁面有關,因而組件更接近于元素。

為何?組件代表頁面上的一個可視元素。 把選擇器定義成 HTML 元素標簽可以與原生 HTML 元素和 WebComponent 保持一致。

為何?查看組件模板的 HTML 時,更容易識別一個符號是組件還是指令。

7.3 把模板和樣式提取到它們自己的文件
  1. 堅持當超過 3 行時,把模板和樣式提取到一個單獨的文件。
  2. 堅持把模板文件命名為[component-name].component.html,其中,[component-name] 是組件名。
  3. 堅持把樣式文件命名為[component-name].component.css,其中,[component-name] 是組件名。

為何?在 (.js.ts) 代碼里面內聯模板時,某些編輯器不支持語法提示。

為何?當沒有與內聯模板和樣式混合時,組件文件中的邏輯更易于閱讀。

7.4 避免重命名輸入和輸出

避免重命名輸入和輸出。

為何?當指令的輸入或輸出屬性的名字與導出的公共 API 名字不一樣時,可能導致混亂。

7.5 成員順序
  1. 堅持把屬性成員放在前面,方法成員放在后面。
  2. 堅持先放公共成員,再放私有成員,并按照字母順序排列。

為何?把類的成員按照統一的順序排列,易于閱讀,能立即識別出組件的哪個成員服務于何種目的。

7.6 把邏輯放到服務里
  1. 堅持在組件中只包含與視圖相關的邏輯。所有其它邏輯都應該放到服務中。
  2. 堅持把可重用的邏輯放到服務中,保持組件簡單,聚焦于它們預期目的。

為何?當邏輯被放置到服務里,并以函數的形式暴露時,可以被多個組件重復使用。

為何?在單元測試時,服務里的邏輯更容易被隔離。當組件中調用邏輯時,也很容易被模擬。

為何?從組件移除依賴并隱藏實施細節。

為何?保持組件苗條、精簡和聚焦。

// app/heroes/hero-list/hero-list.component.ts
/* avoid */
import { OnInit } from '@angular/core';
import { Http, Response } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import { Hero } from '../shared/hero.model';
const heroesUrl = 'http://angular.io';
export class HeroListComponent implements OnInit {
  heroes: Hero[];
  constructor(private http: Http) {}
  getHeroes() {
    this.heroes = [];
    this.http.get(heroesUrl)
      .map((response: Response) => <Hero[]>response.json().data)
      .catch(this.catchBadResponse)
      .finally(() => this.hideSpinner())
      .subscribe((heroes: Hero[]) => this.heroes = heroes);
  }
  ngOnInit() {
    this.getHeroes();
  }
  private catchBadResponse(err: any, source: Observable<any>) {
    // log and handle the exception
    return new Observable();
  }
  private hideSpinner() {
    // hide the spinner
  }
}

// app/heroes/hero-list/hero-list.component.ts
import { Component, OnInit } from '@angular/core';
import { Hero, HeroService } from '../shared';
@Component({
  selector: 'toh-hero-list',
  template: `...`
})
export class HeroListComponent implements OnInit {
  heroes: Hero[];
  constructor(private heroService: HeroService) {}
  getHeroes() {
    this.heroes = [];
    this.heroService.getHeroes()
      .subscribe(heroes => this.heroes = heroes);
  }
  ngOnInit() {
    this.getHeroes();
  }
}
7.7 不要給輸出屬性加前綴
  1. 堅持命名事件時,不要帶前綴on
  2. 堅持把事件處理器方法命名為on前綴之后緊跟著事件名。

為何?與內置事件命名一致,例如按鈕點擊。

為何?Angular 允許另一種備選語法 on-*。如果事件的名字本身帶有前綴on,那么綁定的表達式可能是on-onEvent。

7.8 把表現層邏輯放到組件類里

堅持把表現層邏輯放進組件類中,而不要放在模板里。

為何?邏輯應該只出現在一個地方(組件類里)而不應分散在兩個地方。

為何?將組件的表現層邏輯放到組件類而非模板里,可以增強測試性、維護性和重復使用性。

app/heroes/hero-list/hero-list.component.ts
COPY CODE
/* avoid */
@Component({
  selector: 'toh-hero-list',
  template: `
    <section>
      Our list of heroes:
      <hero-profile *ngFor="let hero of heroes" [hero]="hero">
      </hero-profile>
      Total powers: {{totalPowers}}<br>
      Average power: {{totalPowers / heroes.length}}
    </section>
  `
})
export class HeroListComponent {
  heroes: Hero[];
  totalPowers: number;
}
app/heroes/hero-list/hero-list.component.ts
COPY CODE
@Component({
  selector: 'toh-hero-list',
  template: `
    <section>
      Our list of heroes:
      <toh-hero *ngFor="let hero of heroes" [hero]="hero">
      </toh-hero>
      Total powers: {{totalPowers}}<br>
      Average power: {{avgPower}}
    </section>
  `
})
export class HeroListComponent {
  heroes: Hero[];
  totalPowers: number;
  get avgPower() {
    return this.totalPowers / this.heroes.length;
  }
}

八、數據服務(☆☆☆☆重要)

8.1 分離數據調用
  1. 堅持把數據操作和互動重構到服務里。
  2. 堅持讓數據服務來負責 XHR 調用、本地儲存、內存儲存或者其它數據操作。

為何?組件的職責是為視圖展示或收集信息。它不應該關心如何獲取數據,它只需要知道向誰請求數據。把如何獲取數據的邏輯移動到數據服務里,簡化了組件,讓其聚焦于視圖。

為何?在測試使用數據服務的組件時,可以讓數據調用更容易被測試(模擬或者真實)。

為何?數據服務的實現可能有非常具體的代碼來處理數據倉庫,包括數據頭 (headers)、如何與數據交談或者其它服務 (例如Http)。把邏輯分離到數據服務可以將該邏輯封裝在一個地方,對外部使用者(例如組件)隱藏具體的實施細節。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,431評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,637評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,555評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,900評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,629評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,976評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,976評論 3 448
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,139評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,686評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,411評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,641評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,129評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,820評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,233評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,567評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,362評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,604評論 2 380

推薦閱讀更多精彩內容