angular1.x和ES6开发风格

一、Module

ES6有自己的模块机制,所以我们要通过使用ES6的模块机制来淡化ng的框架,使得各业务逻辑层的看不出框架的痕迹,具体的做法是:
  • 把各功能模块的具体实现代码独立出来。
  • module机制作为一个壳子,对功能模块进行封装。
  • 每个功能分组,使用一个总的壳子来包装,减少上级模块的引用成本。
  • 每个壳子文件把module的name属性export出去。
举例来说,我们有一个moduleA,里面有serviceA,serviceB,那么,就有这样一些文件:
serviceA的实现,service/a.js
  1. export default class ServiceA {}

serviceB的实现,service/b.js

  1. export default class ServiceB {}

moduleA的壳子定义,moduleA.js

  1. import ServiceA from './services/a';
  2. import ServiceB from './services/b';
  3. export default angular.module('moduleA'[])
  4. .service('ServiceA', ServiceA)
  5. .service('ServiceB', ServiceB)
  6. .name;

存在一个moduleB要使用moduleA:

  1. import moduleA from './moduleA';
  2. export default angular.module('moduleB', [moduleA]).name;

二、Controller

ng1.2开始提供了controllerAs的语法,自此Controller终于能变成一个纯净的ViewModel(视图模型)了,而不像之前一样混入过多的$scope痕迹。
例如:
HTML
  1. <div ng-controller="AppCtrl as app">
  2. <div ng-bing="app.name"></div>
  3. <button ng-click="app.getName">get app name</button>
  4. </div>

controller AppCtrl.js

  1. export default class AppCtrl {
  2. constructor() {
  3. this.name = 'angualr$es6';
  4. }
  5. getName() {
  6. return this.name;
  7. }
  8. }

module

  1. import AppCtrl from './AppCtrl';
  2. export default angular.module('app', [])
  3. .controller('AppCtrl', AppCtrl)
  4. .name;

三、Component(Directive)

指令主要包含了一个ddo(Directive Definition Object),所以本质上这是一个对象,我们可以给它构建一个类。
  1. export default class DirectiveA {}

DDO上面的东西大致可以分为两类,属性和方法,所以就在构造函数里这样定义:

  1. constructor() {
  2. this.template = template;
  3. this.restrict = 'E';
  4. }

接下来就是controller和link,compile等函数了,比如controller,可以实现一个普通的controller类,然后赋值到controller属性上来:

  1. this.controller = ControllerA;

写directive的时候,尽量使用controllerAs这样的语法,这样controller可以清晰一些,不必注入$scope,而且还可以使用bingToController属性,把在指令attr上定义的值或方法传递到controller实例上来。接下来我们使用三种方法来定义指令


1、定义一个类(ddo),然后在定义指令的工厂函数中返回这个类的实例。

我们要做一个日期控件,合起来就是这样
  1. import template from '../template/calendar.html';
  2. import CalendarCtrl from '../controllers/calendar';
  3.  
  4. import '../css/calendar.css';
  5.  
  6. export default class CalendarDirective{
  7. constructor() {
  8. this.template = template;
  9. this.restrict = 'E';
  10.  
  11. this.controller = CalendarCtrl;
  12. this.controllerAs = 'calendarCtrl';
  13. this.bingToController = true;
  14.  
  15. this.scope = {
  16. minDate: '=',
  17. maxDate: '=',
  18. selecteDate: '=',
  19. dateClick: '&'
  20. };
  21. }
  22.  
  23. link(scope) {
  24. //这个地方引入了scope,应尽量避免这种做法,
  25. //但是搬到controller写成setter,又会在constructor之前执行
  26. scope.$watch('calendarCtrl.selecteDate', newDate => {
  27. if(newDate) {
  28. scope.calendarCtrl.calendar.year = newDate.getFullYear();
  29. scope.calendarCtrl.calendar.month = newDate.getMonth();
  30. scope.calendarCtrl.calendar.date = newDate.getDate();
  31.  
  32. }
  33. });
  34. }
  35. }

然后在module定义的地方:

  1. import CalendarDirective from './directives/calendar';
  2.  
  3. export default angular.module('components.form.calendar', [])
  4. .directive('snCalendar', () => new CalendarDirective())
  5. .name;

2、直接定义一个ddo对象,然后传给指令

同样以datepicker组件为例,先定义一个controller
  1. // DatePickerCtrl.js
  2. export default class DatePickerCtrl {
  3. $onInit() {
  4. this.date = `${this.year}-${this.month}`;
  5. }
  6.  
  7. getMonth() {
  8. ...
  9. }
  10.  
  11. getYear() {
  12. ...
  13. }
  14. }

注意,这里先写了controller而不是link/compile方法,原因在于一个数据驱动的组件体系下,我们应该尽量减少对DOM操作,因此理想状态下,组件是不需要link或compile方法的,而且controller在语义上更贴合mvvm架构。

在模块定义的地方我们可以这样使用:
  1. import template from './date-picker-tpl.html';
  2. import controller from './DatePickerCtrl';
  3.  
  4. const ddo = {
  5. restrict: 'E',
  6. template, //es6对象简写
  7. controller,
  8. controllerAs: '$ctrl',
  9. bingToController: {
  10. year: '=',
  11. month: '='
  12. }
  13.  
  14. };
  15.  
  16. export default angular.module('components.datePicker', [])
  17. .directive('dataPicker', ddo)
  18. .name;

在整个系统设计中只有index.js(定义模块的地方)是框架可识别的,其它地方的业务逻辑都不应该出现框架的影子,这样方便移植。


3、component

1.5之后提供了一个新的语法moduleInstance.component,它是moduleInstance.directive的高级封装版,提供了更简洁的语法,同时也是未来组件应用的趋势。例如
bingToController -> bindings的变化,而且默认controllerAs = ‘$ctrl’,但是它只能定义自定义标签,不能定义增强属性,而且component定义的组件都是isolated scope。

1.5版本还给组件定义了相对完整的生命周期钩子,而且提供了单向数据流的方式,以上例子可以写成下面这样子:

  1. //DirectiveController.js
  2. export class DirectiveController {
  3. $onInit() {
  4.  
  5. }
  6.  
  7. $onChanges(changesObj) {
  8.  
  9. }
  10.  
  11. $onDestroy() {
  12.  
  13. }
  14.  
  15. $postLink() {
  16.  
  17. }
  18. }
  19.  
  20. //index.js
  21. import template from './date-picker-tpl.html';
  22. import controller from './DatePickerCtrl';
  23.  
  24. const ddo = {
  25. template,
  26. controller,
  27. bindings: {
  28. year: '<',
  29. month: '<'
  30. }
  31. };
  32.  
  33. export default angular.module('components.datepicker', [])
  34. .component('datePicker', ddo)
  35. .name;

4、服务

先来说一下在ES6中通过factory和service来定义服务的方式。
serviceA的实现,service/a.js
  1. export default class ServiceA {}

serviceA的模块包装器moduleA的实现

  1. import ServiceA from './service/a';
  2.  
  3. export angular.module('moduleA', [])
  4. .service('ServiceA', ServiceA)
  5. .name;

factoryA的实现,factory/a.js

  1. import EntityA from './model/a';
  2.  
  3. export default function FactoryA {
  4. return new EntityA();
  5. }

factoryA的模块包装器moduleA的实现

  1. import FactoryA from './factory/a';
  2.  
  3. export angular.module('modeuleA', [])
  4. .factory('FactoryA', FactoryA)
  5. .name;

对于依赖注入我们可以通过以下方式来实现:

controller/a.js
  1. export default class ControllerA {
  2. constructor(ServiceA) {
  3. this.serviceA = ServiceA;
  4. }
  5. }
  6.  
  7. ControllerA.$inject = ['ServiceA'];

  1. import ControllerA from './controllers/a';
  2.  
  3. export angular.module('moduleA', [])
  4. .controller('ControllerA', ControllerA);

对于constant和value,可以直接使用一个常量来代替。

Contant.js
  1. export const VERSION = '1.0.0';

5、filter

angular中filter做的事情有两类:过滤和格式化,归结起来就是一种数据的变换工作,过度使用filter会让你的额代码在不自知的情况下走向混乱。所以我们可以自己去写一系列的transformer来做数据处理。
  1. import { dateFormatter } './transformers';
  2. export default class Controller {
  3. constructor() {
  4. this.data = [1,2,3,4];
  5.  
  6. this.currency = this.data
  7. .filter(v => v < 4)
  8. .map(v => '$' + v);
  9.  
  10. this.date = Date.now();
  11. this.today = dateFormatter(this.date);
  12. }
  13. }

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

1、controller的注入

1.2之后有了controllerAS的语法,我们可以这么写。
  1. <div ng-controller="TestCtrl as testCtrl">
  2. <input ng-model="testCtrl.aaa">
  3. </div>

  1. xxx.controller("TestCtrl", [function() {
  2. this.aaa = 1;
  3. }]);

实际上框架会做一些事情:

  1. $scope.testCtrl = new TestCtrl();

对于这一块,把那个function换成ES6的类就可以了。

2、依赖属性的计算

在$scope上,除了有$watch,$watchGroup,$watchCollection,还有$eval(作用域上的表达式求值)这类东西,我们必须想到对它们的替代办法。

一个$watch的典型例子
  1. $scope.$watch("a", function(val) {
  2. $scope.b = val + 1;
  3. });

我们可以直接使用ES5的setter和getter来定义就可以了。

  1. class A {
  2. set a(val) { //a改变b就跟着改变
  3. this.b = val + 1;
  4. }
  5. }

如果有多个变量要观察,例如

  1. $scope.$watchGroup(["firstName", "lastName"], function(val) {
  2. $scope.fullName = val.join(",");
  3. });

我们可以这样写

  1. class Controller {
  2.  
  3. get fullName() {
  4. return `${this.firstName} ${this.lastName}`;
  5. }
  6. }

html

  1. <input type="text" ng-model="$ctrl.firstName">
  2. <input type="text" ng-model="$ctrl.lastName">
  3.  
  4. <span ng-bind="$ctrl.fullName"></span>

3、事件冒泡和广播

在$scope上,另外一套常用的东西是$emit,$broadcast,$on,这些API其实是有争议的,因为如果说做组件的事件传递,应当以组件为单位进行通信,而不是在另外一套体系中。所以我们也可以不用它,比较直接的东西通过directive的attr来传递,更普遍的东西用全局的类似Flux的派发机制去通信。

根作用域的问题也是一样,尽量不要去使用它,对于一个应用中全局存在的东西,我们有各种策略去处理,不必纠结于$rootScope。

4、指令中$scope

参见上文关于指令的章节。

7、总结

对于整个系统而言,除了angular.module,angular.controller,angular.component,angular.directive,angular.config,angular.run以外,都应该实现成与框架无关的,我们的业务模型和数据模型应该可以脱离框架而运作,当做完这层之后,上层迁移到各种框架就只剩下体力活了。

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

参考链接:






angular1.x + ES6开发风格记录的更多相关文章

  1. ES6深入学习记录(三)编程风格

    今天学习阮一峰ES6编程风格,其中探讨了如何将ES6的新语法,运用到编码实践之中,与传统的JavaScript语法结合在一起,写出合理的.易于阅读和维护的代码. 1.块级作用域 (1)let 取代 v ...

  2. [webpack] 配置react+es6开发环境

    写在前面 每次开新项目都要重新安装需要的包,简单记录一下. 以下仅包含最简单的功能: 编译react 编译es6 打包src中入口文件index.js至dist webpack配置react+es6开 ...

  3. webpack+react+redux+es6开发模式

    一.预备知识 node, npm, react, redux, es6, webpack 二.学习资源 ECMAScript 6入门 React和Redux的连接react-redux Redux 入 ...

  4. iOS开发之记录用户登录状态

    iOS开发之记录用户登录状态 我们知道:CoreData的配置和使用步骤还是挺复杂的.但熟悉CoreData的使用流程后,CoreData还是蛮好用的.今天要说的是如何记录我们用户的登陆状态.例如微信 ...

  5. 开发错误记录8:Unable to instantiate application com

    开发错误记录8:Unable to instantiate application com.android.tools.fd.runtime.BootstrapApplication 这是因为在And ...

  6. 【转】使用gulp 进行ES6开发

    原谅地址:https://segmentfault.com/a/1190000004394726 一说起ES6,总会顺带看到webpack.babel.browserify还有一些认都不认识的blab ...

  7. Arduino单片机使用和开发问题记录(转)

    源:Arduino单片机使用和开发问题记录 1.将程序上传到板子时Arduino IDE提示“avrdude: stk500_getsync(): not in sync: resp=0x00” 网上 ...

  8. webpack+react+redux+es6开发模式---续

    一.前言 之前介绍了webpack+react+redux+es6开发模式 ,这个项目对于一个独立的功能节点来说是没有问题的.假如伴随着源源不断的需求,前段项目会涌现出更多的功能节点,需要独立部署运行 ...

  9. IOS开发之记录用户登陆状态,ios开发用户登陆

    IOS开发之记录用户登陆状态,ios开发用户登陆 上一篇博客中提到了用CoreData来进行数据的持久化,CoreData的配置和使用步骤还是挺复杂的.但熟悉CoreData的使用流程后,CoreDa ...

随机推荐

  1. Oracle中如何插入特殊字符:& 和 ' (多种解决方案)-转载

    文章出处:http://blog.sina.com.cn/s/blog_5f39af320101gb3f.html 今天在导入一批数据到Oracle时,碰到了一个问题:Toad提示要给一个自定义变量A ...

  2. Spring-java-模板设计模式

    1,模板设计模式指的是将相应的模板方法提取出来在专门的位置定义,然后把相同调用过程操作,通过模板来实现对于模板设计模式而言,一般有两种实现方式 1)基于继承的实现 2)基于组合的实现 Spring的J ...

  3. 原型那些事 - JavaScript深入浅出(三)

    前两次总结了JavaScript中的基本数据类型(值类型<引用类型>,引用类型<复杂值>)以及他们在内存中的存储,对内存空间有了一个简单的了解,以及第二次总结了this深入浅出 ...

  4. Shiro初识与总结

    1.1简介 Apache Shiro是一个强大且易用的Java安全框架,执行身份验证.授权.密码学和会话管理.使用Shiro的易于理解的API,您可以快速.轻松地获得任何应用程序,从最小的移动应用程序 ...

  5. spring boot基础 入门

    spring boot基础 spring boot 的简单搭建 spring boot 的基本用法 spring boot 基本用法 自动配置 技术集成 性能监控 源码解析 工程的构建 创建一个mav ...

  6. 基于注解方式实现Aop

    开启注解扫描 <context:component-scan base-package="aopSpring"></context:component-scan& ...

  7. datepickerpopup时间限制选取

    使用popup组件的过程中遇到时间选取的问题 官方文档大致说使用date和mode 可以解决,奈何老夫是看不懂,写的时候参考的有 官方文档.echo2016的博文.liumang361的博文 先看图 ...

  8. 系统学习java高并发系列二

    转载请注明原创出处,谢谢! 什么是线程? 线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程 ...

  9. AJAX学习前奏----JS基础加强

     AJAX学习前奏----JS基础加强 知识概要: 1.js类&属性&方法的定义 2.静态属性与方法 3.构造方法 4.原型的使用 5.Object对象直接加属性和方法 6.JSO ...

  10. Linux向文件添加内容的几种方法

    例如,要想test.txt文件添加内容"I am a boy",test.txt在当前目录中 方法一:vi编辑法 打开终端,输入vi test.txt 回车,按a或i进入编辑模式, ...