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之外,都应该实现成与框架无关的,咱们的业务模型和数据模型应该能够脱离框架而运做,当作完这层以后,上层迁移到各类框架就只剩下体力活了。

一个可伸缩的系统构架,确保下层业务模型/数据模型的纯净都是有必要的,这样才能提供上层随意变化的可能,任何模式下的应用开发,都应具有这样一个能力。


参考连接:
相关文章
相关标签/搜索