轉自本身在開源中國的博客:https://my.oschina.net/u/7247…
angular 1 也要面向組件編程
前端組件化是前端開闢情勢中一個不可逆轉的趨向,三大重要前端框架 angular 2
react
vue
都不謀而合的把組件化編程作為本身的一大賣點,angular 1
作為一個汗青相對悠長的框架,在私生子 angular 2
的推進下,終究也搭上了組件化編程的末班車,公司里那些老項目終究也有時機體驗組件化編程的味道。
angular 1 的組件化之路
angular 1
中類似組件化的編程頭腦實在很早就有,只不過那時候不叫組件,而叫指令(Directive),指定 restrict: 'E'
后這個指令就與如今組件的用法很類似了。angular 1.5
中,又將指令依據 angular 2
的類似觀點加以限定,脫胎為如今的組件(Components)。
組件的特性
官方文檔列舉了組件和指令的差別點。除此之外,一個範例的組件還應相符以下幾個特性。
- 組件的標籤稱號必需包含中劃線
- 組件具有優越的生命周期
- 組件有自包含性
- 組件有自封閉性
- 組件有可復用性
- 組件能夠被定製化
下面順次申明。
組件的稱號範例
與指令差別,組件必需是一個元素,HTML 關於這一點有特別的範例。
HTML 範例把帶有中劃線的標籤留給開闢者運用,如許構成的元素又稱作自定義元素(Custom Element)。我們雖然沒有用到自定義元素的觀點,但二者的行動是類似的。我們應當相符這一規範。
這一點範例對應到 angular 1
中即為:組件稱號必需帶有駝峰情勢。
比方:
module.component('dialog', {
// ...
});
這是不對的。HTML 範例已定義了 dialog 這個規範元素,重複運用標署名能夠致使我們自定義的組件行動和規範元素的行動混淆到一同,致使奇葩 bug;而且假如如許做也間接致使開闢者不能運用原生的 dialog
標籤。
別的,就算如今規範沒有定義某個元素,不代表未來不會定義。我們的順序既然跑在瀏覽器里,就要按禮貌做事。這是一種正當的寫法:
module.component('customDialog', {
// ...
});
組件的自包含性
一個設想優越的組件肯定有它本身的行動和默許款式。
默許行動
默許行動在 angular 1
頂用控制器(Controller)定義。
function CustomDialogController($service) {
this.someField = 123;
this.someMethod = function someMethod() {
}
}
CustomDialogController.$inject = ['$service'];
module.component('customDialog', {
controller: CustomDialogController,
template: require('./customDialogTemplate.html'),
});
由於組件默許啟用 controllerAs
,一切變量和函數都是綁定到 this
上的,所以你也能夠運用 ES2015
的 class
語法來構造代碼:
class CustomDialogController {
constructor($service) {
}
someMethod() {
}
}
CustomDialogController.$inject = ['$service'];
module.component('customDialog', {
controller: CustomDialogController,
template: require('./customDialogTemplate.html'),
});
如許做有一個題目就是其他函數不能運用 constructor
里注入的效勞(Service),只能經由過程 this
中轉一次。我個人的做法是如許:
class CustomDialogController {
constructor($service) {
this.services = { $service };
}
someMethod() {
const { $service } = this.services;
}
}
// 下略
發起關於邏輯相對簡樸的組件的控制器運用 function
定義,龐雜的組件運用 class
定義,後者代碼的層次要更加清楚易讀。
默許款式
組件的默許款式直接運用款式表指定。
custom-dialog {
display: block;
// ...
}
關於一切瀏覽器不認識的標籤,默許都是內聯元素(display: inline
),關於組件來講一般不是想要的。所以自定義的組件一般最少要有 display: (inline-)block
來轉變元素的默許顯現體式格局。
組件的自封閉性
自封閉性包含兩個方面:數據的自封閉性和款式的自封閉性。
數據的自封閉性
angular 1
中,組件本身的 scope 已是斷絕的(isolate),即組件的 scope 不繼續自父級 scope(__proto__
為 null
)。除此之外,一個範例的組件不該當直接運用外部的數據,由於如許會損壞組件的可復用性。舉幾個例子:
- $rootScope
- $root、$parent(模板中)
- 路由參數
- localStorage、sessionStorage
這些數據都應當經由過程參數綁定 binding
傳入。假如組件是路由插件天生,那末能夠用 resolve。
其次,參數綁定不該運用雙向綁定 =
,範例的組件不該(直接)修正組件外部傳入的數據。官方引薦的參數綁定體式格局有兩種
-
<
單向綁定,綁定可變數據。一般用於給組件通報數據 -
@
字符串綁定,綁定字符串。一般用於指定組件行動
關於單向綁定對象的狀況,由因而援用通報,也不該當修正對象內部的屬性。
碰到要向外部傳值的狀況,引薦運用 ngModel 或 事宜綁定(下面會提到)
款式的自封閉性
組件間的款式不該當相互滋擾,這一點能夠簡樸的經由過程 scss
的款式嵌套(Nesting)完成:
custom-dialog {
display: block;
// ...
.title {
// ...
}
.body {
// ...
}
}
如許能夠簡樸的把組件的內置款式表限定在組件內部,從而防止款式外溢。然則這類要領對在組件內部的其他組件不起結果。假如這個組件的模板中還援用了別的組件,或許這個組件被定義為可嵌入的(transclude),那末能夠斟酌加類名前綴:
custom-dialog {
display: block;
.custom-dialog {
&-title {
// ..
}
&-body {
}
}
}
組件的可復用性
組件為復用而生,具有優越自封閉性的組件必定是可復用的,由於這個組件不受任何外部要素滋擾。組件的復用情勢包含
- 一個頁面中運用屢次
- 在多個頁面中運用
ng-repeat
- 本身套本身(遞歸樹結構)
- 全部源代碼拷貝到其他項目中
等等。一個高度可復用的組件則能夠被稱為控件,是能夠零丁投稿 npm
項目庫的。
固然,有些組件(比方零丁的頁面)能夠復用需求沒那末高,能夠視組件的復用程度差別,從組件的自封閉性和團體代碼量做一些棄取。
組件的定製化
一個高度可復用的組件肯定能夠被定製。
行動的定製化
經由過程參數綁定完成組件行動的定製化。比方:
<custom-dialog x-title="My Dialog" x-modal="true"><!-- 與標署名一樣,自定義屬性名也應當運用中劃線 -->
<!--content -->
</custom-dialog>
module.component('customDialog', {
template: require('./customDialogTemplate.html'),
transclude: true,
bindings: {
title: "@",
modal: '<',
},
});
出於運用方便的斟酌,定製用的參數都是可選的,組件內部完成應當給每一個定製參數設定默許值。
款式的定製化
組件作風定製能夠運用 class 推斷。
custom-dialog {
display: block;
// ...
.title {
font-size: 16px;
// ...
}
&.big {
.title {
font-size: 24px;
}
}
}
運用時
<custom-dialog x-title="My Dialog" class="mydialog big"></custom-dialog>
深度定製款式比較好的體式格局是 CSS 屬性(CSS Variable,注重不是 SCSS 屬性)。
custom-dialog {
display: block;
// ...
.title {
font-size: 16px;
color: var(--dialog-title-color, #333);
// ...
}
&.big {
.title {
font-size: 24px;
}
}
}
這時候只需要文檔中申明題目色彩運用 --dialog-title-color
這個 CSS 變量就好,外部運用不依賴於組件內部 DOM 完成。運用時
.mydialog {
--dialog-title-color: red;
}
組件的生命周期
從建立至燒毀,組件有本身的生命周期(lifecycle),而不像指令那樣把 scope 作為生命周期。經常使用的回調函數以下:
-
$onInit()
:組件被初始化時挪用。與 constructor 差別,angular 1
確保$onInit
被挪用時組件的一切參數綁定都被準確賦值。 -
$onChanges(changeObj)
:組件參數綁定值被轉變時挪用。用於監聽綁定值的變化,首次綁定時也會挪用這個函數。 -
$onDestroy()
:組件被燒毀時挪用。用於清算內部資本如$interval
等。
這些函數也是綁定在 this
上的。假如 controller
運用 ES2015
的 class
定義體式格局,能夠這麼寫:
class CustomDialogController {
constructor() {}
onInit() {}
onChanges({ prop1, prop2 }) {}
onDestroy() {}
}
組件間的通訊
組件間通訊是一個讓很多人頭疼的題目,一般有如許 3 種狀況
子 -> 父
這類狀況有規範的完成體式格局:事宜綁定。比方
class CustomDialogController {
close($value) {
this.hide = true;
this.onClose({ $value });
}
}
module.component('customDialog', {
controller: CustomDialogController,
template: require('./customDialogTemplate.html'),
bindings: {
onClose: '&',
},
});
運用時:
<custom-dialog on-close="$ctrl.handleClose(value)"></custom-dialog>
這類體式格局也能夠用於子組件向父組件傳值。
父 -> 子
用於觸發子組件的某個行動。除了轉變某個在子組件內部監聽變化的綁定參數值外,卓有成效的體式格局就只有事宜播送。
子組件先監聽某個事宜
$scope.$on('custom-dialog--close', () => this.close());
父組件發送播送
$scope.$broadcast('custom-dialog--close');
牢記:事宜是全局性的。當有組件復用的狀況時請運用標識指定吸收對象(BUS 模子);別的最好給事宜名增加組件前綴。
同級組件
請經由過程父級組件中轉
子 -> 某全局性組件
這個顯現 Notification 時最經常使用。碰到這類狀況時,能夠封裝效勞(Service)。比方:
module.component('globalNotification', {
controller: class GlobalNotificationController {
constructor(notificationService) {
notificationService.component = this;
}
show(props) {
// ...
}
}
});
module.factory('notify', function NotifyService() {
return {
warn(msg) {
this.show({ type: 'warn', text: msg });
}
error(msg) {
this.show({ type: 'error', text: msg });
}
}
});
計劃並不圓滿。假如有更好的發起迎接提出。
結語
有人能夠問既然三大前端框架都是組件化的,何須還要在 angular 1
上完成。卻不知 angular 1
的組件降生的初志就是為了削減向 angular 2
遷徙的難度。時機老是留給有預備的人,哪天老闆大發慈悲示意給你把代碼重寫的時候,你卻看着項目里滿屏的 $scope.abc = xxx
手足無措,這豈不是悲劇。。。