angular1.x + ES6開發風格記錄

angular1.x和ES6開發風格

1、Module

ES6有本身的模塊機制,因此咱們要經過使用ES6的模塊機制來淡化ng的框架,使得各業務邏輯層的看不出框架的痕跡,具體的作法是:
  • 把各功能模塊的具體實現代碼獨立出來。
  • module機制做爲一個殼子,對功能模塊進行封裝。
  • 每一個功能分組,使用一個總的殼子來包裝,減小上級模塊的引用成本。
  • 每一個殼子文件把module的name屬性export出去。
舉例來講,咱們有一個moduleA,裏面有serviceA,serviceB,那麼,就有這樣一些文件:
serviceA的實現,service/a.js
export default class ServiceA {}
serviceB的實現,service/b.js
export default class ServiceB {}
moduleA的殼子定義,moduleA.js
import ServiceA from './services/a';
import ServiceB from './services/b';
export default angular.module('moduleA'[])
	.service('ServiceA', ServiceA)
	.service('ServiceB', ServiceB)
	.name;
存在一個moduleB要使用moduleA:
import moduleA from './moduleA';
export default angular.module('moduleB', [moduleA]).name;

2、Controller

ng1.2開始提供了controllerAs的語法,自此Controller終於能變成一個純淨的ViewModel(視圖模型)了,而不像以前同樣混入過多的$scope痕跡。
例如:
HTML
<div ng-controller="AppCtrl as app">
	<div ng-bing="app.name"></div>
	<button ng-click="app.getName">get app name</button>
</div>
controller AppCtrl.js
export default class AppCtrl {
	constructor() {
		this.name = 'angualr$es6';
	}
	getName() {
		return this.name;
	}
}
module
import AppCtrl from './AppCtrl';
export default angular.module('app', [])
	.controller('AppCtrl', AppCtrl)
	.name;

3、Component(Directive)

指令主要包含了一個ddo(Directive Definition Object),因此本質上這是一個對象,咱們能夠給它構建一個類。
export default class DirectiveA {}
DDO上面的東西大體能夠分爲兩類,屬性和方法,因此就在構造函數裏這樣定義:
constructor() {
	this.template = template;
	this.restrict = 'E';
}
接下來就是controller和link,compile等函數了,好比controller,能夠實現一個普通的controller類,而後賦值到controller屬性上來:
this.controller = ControllerA;
寫directive的時候,儘可能使用controllerAs這樣的語法,這樣controller能夠清晰一些,沒必要注入$scope,並且還可使用bingToController屬性,把在指令attr上定義的值或方法傳遞到controller實例上來。接下來咱們使用三種方法來定義指令

一、定義一個類(ddo),而後在定義指令的工廠函數中返回這個類的實例。

咱們要作一個日期控件,合起來就是這樣
import template from '../template/calendar.html';
import CalendarCtrl from '../controllers/calendar';

import '../css/calendar.css';

export default class CalendarDirective{
	constructor() {
		this.template = template;
		this.restrict = 'E';

		this.controller = CalendarCtrl;
		this.controllerAs = 'calendarCtrl';
		this.bingToController = true;

		this.scope = {
			minDate: '=',
			maxDate: '=',
			selecteDate: '=',
			dateClick: '&'
		};
	}

	link(scope) {
		//這個地方引入了scope,應儘可能避免這種作法,
		//可是搬到controller寫成setter,又會在constructor以前執行
		scope.$watch('calendarCtrl.selecteDate', newDate => {
			if(newDate) {
				scope.calendarCtrl.calendar.year = newDate.getFullYear();
				scope.calendarCtrl.calendar.month = newDate.getMonth();
				scope.calendarCtrl.calendar.date = newDate.getDate();

			}
		});
	}
}
而後在module定義的地方:
import CalendarDirective from './directives/calendar';

export default angular.module('components.form.calendar', [])
    .directive('snCalendar', () => new CalendarDirective())
    .name;

二、直接定義一個ddo對象,而後傳給指令

一樣以datepicker組件爲例,先定義一個controller
// DatePickerCtrl.js
export default class DatePickerCtrl {
	$onInit() {
		this.date = `${this.year}-${this.month}`;
	}

	getMonth() {
		...
	}

	getYear() {
		...
	}
}
注意,這裏先寫了controller而不是link/compile方法,緣由在於一個數據驅動的組件體系下,咱們應該儘可能減小對DOM操做,所以理想狀態下,組件是不須要link或compile方法的,並且controller在語義上更貼合mvvm架構。
在模塊定義的地方咱們能夠這樣使用:
import template from './date-picker-tpl.html';
import controller from './DatePickerCtrl';

const ddo = {
	restrict: 'E',
	template, //es6對象簡寫
	controller,
	controllerAs: '$ctrl',
	bingToController: {
		year: '=',
		month: '='
	}

};

export default angular.module('components.datePicker', [])
    .directive('dataPicker', ddo)
    .name;
在整個系統設計中只有index.js(定義模塊的地方)是框架可識別的,其它地方的業務邏輯都不該該出現框架的影子,這樣方便移植。

三、component

1.5以後提供了一個新的語法moduleInstance.component,它是moduleInstance.directive的高級封裝版,提供了更簡潔的語法,同時也是將來組件應用的趨勢。例如
bingToController -> bindings的變化,並且默認 controllerAs = ‘$ctrl’,可是它只能定義自定義標籤,不能定義加強屬性,並且component定義的組件都是isolated scope。

1.5版本還給組件定義了相對完整的生命週期鉤子,並且提供了單向數據流的方式,以上例子能夠寫成下面這樣子:
//DirectiveController.js
export class DirectiveController {
	$onInit() {

	}

	$onChanges(changesObj) {

	}

	$onDestroy() {

	}

	$postLink() {

	}
}

//index.js
import template from './date-picker-tpl.html';
import controller from './DatePickerCtrl';

const ddo = {
	template,
	controller,
	bindings: {
		year: '<',
		month: '<'
	}
};

export default angular.module('components.datepicker', [])
    .component('datePicker', ddo)
    .name;

四、服務

先來講一下在ES6中經過factory和service來定義服務的方式。
serviceA的實現,service/a.js
export default class ServiceA {}
serviceA的模塊包裝器moduleA的實現
import ServiceA from './service/a';

export angular.module('moduleA', [])
    .service('ServiceA', ServiceA)
    .name;

factoryA的實現,factory/a.js
import EntityA from './model/a';

export default function FactoryA {
	return new EntityA();
}
factoryA的模塊包裝器moduleA的實現
import FactoryA from './factory/a';

export angular.module('modeuleA', [])
    .factory('FactoryA', FactoryA)
    .name;

對於依賴注入咱們能夠經過如下方式來實現:
controller/a.js
export default class ControllerA {
	constructor(ServiceA) {
		this.serviceA = ServiceA;
	}
}

ControllerA.$inject = ['ServiceA'];

import ControllerA from './controllers/a';

export angular.module('moduleA', [])
    .controller('ControllerA', ControllerA);

對於constant和value,能夠直接使用一個常量來代替。
Contant.js
export const VERSION = '1.0.0';

五、filter

angular中filter作的事情有兩類:過濾和格式化,歸結起來就是一種數據的變換工做,過分使用filter會讓你的額代碼在不自知的狀況下走向混亂。因此咱們能夠本身去寫一系列的transformer來作數據處理。
import { dateFormatter } './transformers';
export default class Controller {
	constructor() {
		this.data = [1,2,3,4];

		this.currency = this.data
		    .filter(v => v < 4)
		    .map(v => '$' + v);

		this.date = Date.now();
		this.today = dateFormatter(this.date);
	}
}

六、消除$scope,淡化框架概念

一、controller的注入

1.2以後有了controllerAS的語法,咱們能夠這麼寫。
<div ng-controller="TestCtrl as testCtrl">
    <input ng-model="testCtrl.aaa">
</div>

xxx.controller("TestCtrl", [function() {
    this.aaa = 1;
}]);
實際上框架會作一些事情:
$scope.testCtrl = new TestCtrl();
對於這一塊,把那個function換成ES6的類就能夠了。

二、依賴屬性的計算

在$scope上,除了有$watch,$watchGroup,$watchCollection,還有$eval(做用域上的表達式求值)這類東西,咱們必須想到對它們的替代辦法。

一個$watch的典型例子
$scope.$watch("a", function(val) {
    $scope.b = val + 1;
});
咱們能夠直接使用ES5的setter和getter來定義就能夠了。
class A {
    set a(val) { //a改變b就跟着改變
        this.b = val + 1;
    }
}
若是有多個變量要觀察,例如
$scope.$watchGroup(["firstName", "lastName"], function(val) {
    $scope.fullName = val.join(",");
});
咱們能夠這樣寫
class Controller {
    
    get fullName() {
        return `${this.firstName} ${this.lastName}`;
    }
}

html
<input type="text" ng-model="$ctrl.firstName">
<input type="text" ng-model="$ctrl.lastName">

<span ng-bind="$ctrl.fullName"></span>

三、事件冒泡和廣播

在$scope上,另一套經常使用的東西是$emit,$broadcast,$on,這些API實際上是有爭議的,由於若是說作組件的事件傳遞,應當以組件爲單位進行通訊,而不是在另一套體系中。因此咱們也能夠不用它,比較直接的東西經過directive的attr來傳遞,更廣泛的東西用全局的相似Flux的派發機制去通訊。javascript

根做用域的問題也是同樣,儘可能不要去使用它,對於一個應用中全局存在的東西,咱們有各類策略去處理,沒必要糾結於$rootScope。css


四、指令中$scope

參見上文關於指令的章節。


七、總結

對於整個系統而言,除了angular.module,angular.controller,angular.component,angular.directive,angular.config,angular.run之外,都應該實現成與框架無關的,咱們的業務模型和數據模型應該能夠脫離框架而運做,當作完這層以後,上層遷移到各類框架就只剩下體力活了。

一個可伸縮的系統構架,確保下層業務模型/數據模型的純淨都是有必要的,這樣才能提供上層隨意變化的可能,任何模式下的應用開發,都應具有這樣一個能力。


參考連接:
相關文章
相關標籤/搜索