使用 ES2015 在 Angular 1.5 中的最佳實踐
說到關于 Angular Styleguide,很多人可能會想到這篇經典的文章。的確,它是一篇非常棒的文章,甚至已經被翻譯成許多種語言(包括中文),在 github 上更是擁有將近 1.9w 個 star。
然而,這次談論的不是它。因為隨著 ES6 的廣泛應用,以及 Angular 1.5 的發布,它有那么一點點不夠時髦(也談不上過時哈~)。
本文的大部分觀點都來自這篇文章(以下簡稱原文),但個人根據工作上積累的一些經驗添并不是完全認同原文的所有想法,并想去除些繁冗的例子,于是就沒有直接翻譯原文。
言歸正傳,下面就來看看使用 ES6 來編寫基于 Angular 1.5 的代碼有哪些最佳實踐。
模塊架構
在 Angular 體系中,所有代碼都是基于模塊的,它來封裝模塊內部的邏輯、模板、路由和子模塊。
模塊劃分
原文將模塊分為 3 大類,分別是:root, component 和 common,并創建相應的文件夾來儲存。
- root:根模塊組件,用來啟動應用和相應模板
- component:包含所有可重用的模塊,模塊中可以包含 components, controllers, services, directives, filters and tests
- common:包含所有業務的模塊(即不可重用,和 component 最大的區別),它可以是頁面布局、導航和頁腳等等。
原文中有詳細的例子,但就如文章開頭所說,在這里就不貼了。
但是,我并不完全認同原文觀點。
因為,common 的翻譯是公共的,在 common 中存放業務代碼也和我們一直以來的做法相悖;其次是,在 Angular 的開發過程中,還是存在一些可以在業務邏輯中公用的代碼,比如 service 和 filter。所以,我更傾向于將它分為 4 部分,分別是 root, app, component 和 common。
- root:和原文的作法一樣,依舊是用來啟動應用,并包含了應用的模板(并不一定要一個文件夾,可以是根目錄下的一個 app.js 文件)
- app:類似于之前的 common 模塊,包含所有的業務模塊組件
- component:同原文的一樣,包含所有可重用的模塊組件
- common:公用代碼模塊,包含可公用的代碼,如 service 和 filter
模塊導出
使用 ES6 肯定會使用強大的模塊語法,在同 Angular 一同使用時,一定要注意導出的是模塊的名字,而非是 Angular 的模塊對象,這樣才能再另一處被其他模塊注入。
// 精簡了原文的代碼,去除了一些和這節無關的代碼
import angular from 'angular';
import CalendarComponent from './calendar.component';
const calendar = angular
.module('calendar', [])
.component('calendar', CalendarComponent)
.name;
export default calendar;
文件命名
首先,為每個模塊添加 index.js
文件來定義整個模塊,這樣再別的模塊中可以通過文件夾直接引入。
原文使用模塊名.文件內容.文件類型
的方式來命名一個文件,如 calendar.controller.js 等。
我完全同意第一個觀點,但第二個中的模塊名就沒有添加的必要,因為文件夾名已經很好的體現了模塊名這個含義。
組件(Component)
組件是 Angular 1.5 新提出的,是一種特殊的指令,Augular 的源碼中也彰顯了這一點。
它相比指令更多的是數據的單向綁定和生命周期鉤子,盡管我認為所謂的生命周期鉤子只是語法糖,甚至組件它本身就是個語法糖,但這不妨礙它成為 Angular 體系中重要的一部分。因為,它的推出明確的區分了指令和組件,解決了原先指令劃分不清、承擔過多工作的問題。
組件屬性
Property | Support |
---|---|
bindings | Yes, 只使用 @ , < , & ,避免使用 =
|
controller | Yes |
controllerAs | Yes, 默認為 $ctrl
|
require | Yes |
template | Yes |
templateUrl | Yes |
transclude | Yes |
控制器(controller)
控制器只應在組件中使用,如果你只想創建一個控制器,那你應創建一個無狀態組件來管理它。
使用 class
關鍵字來創建控制器時要注意以下幾點:
- 使用
constructor
處理依賴注入 - 之前提到過,導出模型名,而并不是直接導出模型
- 使用箭頭函數
- 使用
$onInit
,$onChanges
,$postLink
和$onDestroy
生命周期
(注意:$onChanges
會在$onInit
之前被調用) - 使用默認的控制器
$ctrl
,不使用controllerAs
修改控制器的別名
單向數據流
- 總是使用
<
單向數據綁定來代替=
雙向數據綁定 - 使用
$onChanges
來監聽數據的變化 - 父組件的方法使用
$event
作為參數傳遞的名字 - 子組件調用時返回一個包含有
$event
屬性的對象
這是不是看上去很像 Redux?沒錯,原文的作者也是推薦使用 Angular Redux 來管理狀態。
狀態組件(Stateful components)和無狀態組件(Stateless components)
狀態組件和無狀態組件其實分別對應了 Redux 中的容器組件(Smart/Container Components)和展示組件(Dumb/Presentational Components),這部分原作者主要也是表達了在 Angular 中實現單向數據流的理念,但原作者提供的例子并不是完整的 Redux,它沒有單一的 Store 和 Reducer。
指令(Directive)
相信指令大家都很熟悉了,但自從 Angular 1.5 提供了組件,指令的選擇就應當慎重考慮,它應當只在裝飾 DOM 時使用。
- 不使用
template
,templateUrl
,scope
,bindToController
或controller
等相關的屬性,如果想用,考慮是不是它可以用component
來實現 - 總是使用
restrict: 'A'
指令屬性
Property | 是否使用 | Why |
---|---|---|
bindToController | No | 使用組件替代 |
compile | Yes | DOM 操作/事件的預處理 |
controller | No | 使用組件替代 |
controllerAs | No | 使用組件替代 |
link functions | Yes | DOM 操作/事件的處理 |
multiElement | Yes | See docs |
priority | Yes | See docs |
require | No | 使用組件替代 |
restrict | Yes | 總是使用 restrict: 'A'
|
scope | No | 使用組件替代 |
template | No | 使用組件替代 |
templateNamespace | Yes (如果必須) | See docs |
templateUrl | No | 使用組件替代 |
transclude | No | 使用組件替代 |
服務(Service)
服務主要用于封裝一些不應在組件中處理的業務邏輯和請求。
Angular 提供 2 種創建服務的方式 service
和 factory
。在 ES6 引入了 class
關鍵字后,它能非常友好地同 service
一起工作,所以,無論何時都使用 service
來創建服務。
類 or 方法
原文的標題是常量或類(Constants or Classes),容許我自作主張的修改一下標題,因為我認為原文的實現的區別更主要的在于是使用類或方法去定義一個服務或控制器等。
當然這兩種方法都可以,因為類它本身就是方法的一個語法糖。但是,Angular 2 是重度依賴 class
關鍵字的,所以,我認為還是全部統一使用 class
關鍵字來聲明服務、控制器、過濾器、指令和組件的定義等。
值得注意的是,Angular 組件和指令定義的參數是一個對象,所以在使用 class
定義時,要手動實例化它。
工具
最后,原文作者還推薦了一些工具
- Babel:編譯工具,這就不多說了,必備神器
- TypeScript:還是為了 A2
- Webpack:打包工具,用過都說好
- ngAnnotate:自動依賴注入,和打包工具一起服用效果更好
- Angular Redux:狀態管理
以上為個人觀點,歡迎交流。