Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Angular的变革 #25

Open
xufei opened this issue Nov 14, 2015 · 11 comments
Open

Angular的变革 #25

xufei opened this issue Nov 14, 2015 · 11 comments

Comments

@xufei
Copy link
Owner

xufei commented Nov 14, 2015

作为Web前端,你幸福吗?

每隔18个月,前端都要难一倍。

每一年,前端都会冒出更多的概念,更多的框架/库,更多的实践。

2015年,前端有哪些东西转入流行?

  • ES2015,Babel
  • 以React和Vue为代表的前端组件化框架

但是,在这关键的一年里,Angular社区相对来说,有些沉寂了,主要原因是Angular自身处于一个剧烈变革期,1.x已经趋向成熟,难有本质提升,而2.0尚未有正式发布的消息。

2015年被黑得最多的主流前端组件化框架是什么?Angular。

黑得最漂亮的一句:

其实是 java 程序员把他们习惯的那一套『仪式感』带入了前端框架导致的。
——尤雨溪

Angular最近在搞的2.0版本,变更可谓剧烈,几乎完全是个新框架。但如果我们关注过1.x版本的演进,就会发现,它也曾经历过不少变革。我们所能看到的变化,其实都是有伏笔的,而Angular官方也在努力做一些事情,让这两代之间能尽量衔接起来。

每个框架在发展过程中,都经历过一次一次的自我革新,那么,Angular经历了什么?

  • controllerAs (1.2)
  • One-time bindings (1.3)
  • new router (1.4)
  • decorator (1.4)
  • components (1.5)
  • …… (2.0)

这些变更,分别都有怎样的含义呢?

业务模型的纯化

在1.2版本之前,我们这样写一个控制器:

angular.module("app").controller("DemoCtrl", ["$scope", function($scope) {
    $scope.arr = [0, 1, 2, 3];

    $scope.addItem = function() {
        $scope.arr.push($scope.arr.length); 
    };
}]);

然后这样使用它:

<div ng-controller="DemoCtrl">
    <button ng-click="addItem()">add item</button>
    <ul>
        <li ng-repeat="item in arr">{{item}}</li>
    </ul>
</div>

在1.2版本之后,我们有了controllerAs,可以不必注入$scope了:

angular.module("app").controller("DemoCtrl", [function() {
    this.arr = [0, 1, 2, 3];

    this.addItem = function() {
        this.arr.push(this.arr.length); 
    };
}])

然后这样使用它:

<div ng-controller="DemoCtrl as demo">
    <button ng-click="demo.addItem()">add item</button>
    <ul>
        <li ng-repeat="item in demo.arr">{{item}}</li>
    </ul>
</div>

更进一步,还可以把上面的逻辑代码改造成这样:

angular.module("app").controller("DemoCtrl", [Demo]);

function Demo() {
    this.arr = [0, 1, 2, 3];
}

Demo.prototype.addItem = function() {
    this.arr.push(this.arr.length); 
};

经过这样的转变,我们可以发现,原先的“controller”很清晰了,变成了很纯净的视图模型。这给我们带来的好处是,这一层的东西更容易测试和迁移。

使用controllerAs语法还有一个好处,可以做到逻辑代码的跨版本通用性,甚至是跨框架通用性。

性能优化

在Angular 1.x的整个发展过程中,一直有人在质疑它的性能,为此,开发组也进行了大量的优化。

因为1.x采用的是脏检查的方式来判断数据变更,所以,如何提升变动项的查找会是一件比较重要的事。从1.0到1.4,几乎每个版本都在这个方面作了一些提升,尽可能压榨出更高的性能来。

Angular也添加了诸如单次绑定之类的特性,以减少对初次加载,但不再变更的变量的追踪。

业界有不少类似的框架使用的是存取器做数据变更观测,Angular2使用zone.js来观测数据变更。

脏检查的原理是:我们所有的对数据的赋值,都是在某些特性场景下触发的,比如:

  • UI事件
  • 网络事件
  • 定时器

如果在每次操作之后,对数据保留一份复制,然后下一次再有事件发生的时候,把新老数据进行比对,就可以判定哪些数据产生了变更,从而可以更新关联的界面。

而zone.js更像是一种“多线程”的技术。它把数据的变更过程利用worker切换出去,等执行完了再更新回来,这样就不会阻塞主线程,这是一个非常有创意的做法,因此,Angular2的渲染性能是比较好的。

这种理念在Web开发中前所未有,但是其实在其他一些客户端领域早有实践。

组件化的开发理念

在Angular 1.4之前版本中,并未刻意强调组件化的理念,业务开发人员拥有较高的自由度,比如说,可以选择使用directive,用自定义元素、自定义属性的方式来实现一定程度的组件化,也可以直接使用ng-include和路由,以比较松散的方式完成业务功能。

但是在1.5版本中,新的组件注册语法诞生了,这就是components。

angular.module("app", []).component('counter', {
    bindings: {
        count: '='
    },
    controller: function () {
        function increment() {
            this.count++;
        }
        function decrement() {
            this.count--;
        }
        this.increment = increment;
        this.decrement = decrement;
    },
    template: [
        '<div class="todo">',
        '<input type="text" ng-model="counter.count">',
        '<button type="button" ng-click="counter.decrement();">-</button>',
        '<button type="button" ng-click="counter.increment();">+</button>',
        '</div>'
    ].join('')
});

这样使用:

<div ng-controller="CountCtrl as vm">
    <counter count="vm.count"></counter>
</div>

在Angular 2中,组件化更是变成了一种强制的理念。一个组件包含以下部分:

  • 模板
  • 控制器
  • 可选的路由

注意到在这里,我们不再有controller,service,directive这些概念,因为都已经转化为纯粹的ES模块。其中,组件可大致对等于以前的directive,只是配置方式更加友好了。

@Component({
    selector: 'basic-routing',
    directives: [ ROUTER_DIRECTIVES], 
    template: `<a [router-link]="['/Home']">Home</a>
              <a [router-link]="['/ProductDetail']">Product Details</a>
              <router-outlet></router-outlet>` 
})
@RouteConfig([
    {path: '/',        component: HomeComponent, as: 'Home'}, 
    {path: '/product/', component: ProductDetailComponent, as: 'ProductDetail'  } 
])
class RootComponent{}

尽管粗略看上去,这段代码会比较奇特,但你可以这么想:主体逻辑都是放在普通的class里,剩下的组件相关的配置放在注解中。这样一想,就没有那么别扭了。

更灵活的路由

Angular 1.x早期自带的路由ngRoute比较简单,可以满足最基本的业务开发需求。

但是,在很多较复杂业务中,子路由成为了比较迫切的需求,我们可能会需要路由的嵌套,或者平级存在多个路由,因此,很多开发者选用了第三方的路由库uiRouter。

这两种路由配置方式都是典型的集中式配置,集中式路由在跟踪、定位等方面有优势,但绝大部分情形下,不灵活。

路由的实质是什么?是组件关系的一种映射,既然是这样,集中化的配置会导致,每当组件包含关系有变化,就可能需要修改全局配置,这是不太好的。

如果构建一个全组件化的系统,我们每个组件实际上只关注自身和所包含的子组件的url映射,基于这个理念,就有了Angular2的路由系统。

Angular2的路由是组件式路由,分散定义在每个组件上,并且,管理了所在组件的一些生命周期。

比如上一节的例子中,我们可以看到:

@RouteConfig([
    {path: '/',        component: HomeComponent, as: 'Home'}, 
    {path: '/product/', component: ProductDetailComponent, as: 'ProductDetail'  } 
])

这段代码是个路由配置,指明了本组件下属两个组件,分别有不同的url,它们会被加在到模板中的router-outlet部分中:

template: `<a [router-link]="['/Home']">Home</a>
    <a [router-link]="['/ProductDetail']">Product Details</a>
    <router-outlet></router-outlet>` 

在1.4版本中,Angular引入了ngNewRouter,实际上这个就是Angular2的兼容版本,理念完全一致。

基于这套路由机制,我们可以通过canDeactivate,deactivate,canActivate,activate等方法来更好地控制组件的生命周期。

开发语言的升级

在使用Angular 1.x的时候,我们可以使用ES3进行开发,也可以使用ES5,尤其是后者,目前绝大部分主流浏览器都支持,所以可以直接使用,使用ES5编写纯逻辑代码会是一件比较舒服的事情。

但我们也可以用ES6,CoffeeScript,TypeScript之类的语言去编写Angular 1.x应用,只是需要进行一些转化,因为它们不是Angular 1.x的默认开发语言。

到2.0的时代,官方推荐用来开发Angular应用的语言就变成了TypeScript和ES6了,严格的语法检查和各种增强特性,使得开发过程变得更加准确高效。

到现在这个时间点,因为Babel之类转译工具的极大发展,前端又普遍对构建过程逐渐习惯,使用ES6和TypeScript的好处已经远远大于坏处了,所以可以从现在开始就立刻切换到这些语言,无论是在用Angular 1.x还是将来要使用2.0。

编程模型的改变

如果用过Angular2的HTTP模块,会发现跟1.x版本的已经很大不同了。

假如我们要实现一个mapData,从远程请求到一个数值数组,把里面每个元素乘以2之后,再传递给下一个方法。

在1.x里,我们是这样写的:

function mapData() {
    return $http.get(url)
        .then(result => result.data)
        .then(data => data.map(item => item * 2));

//下面的写法不必要,因为内部非异步,感谢@imcotton提醒
/*
    var defer = $q.defer();
    $http.get(url).then(function(result) {
        var newData = result.map(function(item) {
            return item * 2;
        });

        defer.resolve(newData);
    });
    return defer.promise;
*/
}

但是在2里面,是这样写:

function mapData() {
    return Http.get(url).map(item =>item * 2);
}

可以看到,这两者有不少差别。1.x的$http.get方法,返回结果是个Promise,所以,如果要持续传递下去,我们也要新建一个Promise并且返回。但是在2里面,Http.get的返回类型是RX Observable,它对很多东西的处理方式会不太一样,所以业务代码的写法也会有所不同,从代码上看,会有很明显的简化。

粗略一看,可能觉得这个RX Observable的例子没什么奇特的,即使你返回一个普通的数组,它也可以map啊,可以reduce,可以filter之类,仍然能传递下去,但RX的这个还可以subscribe之类,像这样:

Http.get(url).map(item => item * 2)
    .subscribe(result => {
        this.todoList.push(result);
    });

这类特性能很大程度上减少我们实现业务功能所需的代码量。

参阅:RxJS

小结

综合以上,我们发现Angular从1.x到2.0的发展过程中,出现了这样一些变革:

  • 视图模型的纯化
  • 性能的优化
  • 组件化的开发理念
  • 更灵活的路由
  • 开发语言的升级
  • 编程模型的改变

这些变革体现了Angular在往一个强大而灵活,复杂而高效的前端组件化框架方向努力。

对自我的彻底革新,并不代表过去“错了”,而是代表过去曾经辉煌过的一些东西,随着时代的发展,渐渐走向过时。如果一个东西不随着时代的发展而修正自己,很快就会被历史的车轮无情碾过。(上面一句请勿联想,不主动不拒绝不承认不负责)

@xufei
Copy link
Owner Author

xufei commented Nov 14, 2015

2015年11月15日,南京GDG,幻灯片:http://xufei.github.io/slides/2015/revolution-of-angular.html#0

@sinoon
Copy link

sinoon commented Nov 14, 2015

感谢前辈分享,请问ES6大规模开始使用大约会在什么时候?现在学Angular1.X是不是已经没用了?还有想学好Angular2.0,一定要会ES6或者TypeScript么?

@kunl
Copy link

kunl commented Nov 15, 2015

@sinoon 没必要必须会 es6 或者 TypeScript ,es5 也可以开发, 官方示例提供的就包括 TypeScript 和 es5 两种写法。

@imcotton
Copy link

纠正,1.x 里 $http 返回的 Promise 可以直接使用,并不需要 wrapper

angular.module('App', ['ngMockE2E']).run(function ($httpBackend, $http) {

    $httpBackend.whenGET('/list').respond([1, 2, 3]);

    function mapData(url) {
        return $http.get(url)
            .then(result => result.data)
            .then(data => data.map(item => item * 2))
        ;
    }

    mapData('/list').then(alert);  // [2, 4, 6]

});

@xufei
Copy link
Owner Author

xufei commented Nov 16, 2015

@sinoon 目前如果要用的话,1.x还是可以用,但写的时候要注意淡化框架相关的一些东西,这个后面我再写一篇来详细说。

虽然2.0可以用ES5来开发,但强烈建议你学ES6,未来几年内,这是必备技能。至于TypeScript,用它会有好处,也有负担,看你团队意愿了。

@xufei
Copy link
Owner Author

xufei commented Nov 16, 2015

@imcotton 感谢提醒,忘了这里内部不是异步了。。。

@Saviio
Copy link

Saviio commented Nov 16, 2015

之前在CFF看到民工老师你用ES6 class写directive,感觉一下亲切不少,因此相比之下1.5新推出的component syntax 似乎也没那么具有吸引力了。

最近看了几个ng-connect的vedio,让我感觉ng和react斗艳也挺好的,ng2也从react里借鉴了一些设计,功能模块设计的更加灵活,之前就看到有人这样评价:

Angular v2 doesn't seem like a "framework", but more like a library that sits on top of the web standards.

再加上了引入了ES6 & TS,进一步减少了JS本身的语法噪音,感觉代码一下子清爽、规整了很多。

至于最后一句...这算是黑么...(您要是不加注释,说不定我就不联想了....)

@zeroone001
Copy link

你给出的1.2版本之后的那个controller As ,你的module函数的第二个参数没加,这个地方会报错;改正之后会出现repeat指令的一个错误,https://docs.angularjs.org/error/ngRepeat/dupes?p0=item%20in%20attr&p1=number:5&p2=5 ;解决方法是ng-repeat="item in demo.arr" 改为 ng-repeat="item in demo.arr track by $index"

@xufei
Copy link
Owner Author

xufei commented Nov 19, 2015

@zeroone001 module后面的那个引用数组吗?我是默认这个模块已定义了。。。如果单独跑的话,要写

angular.module("app", [])

repeat错误这个,是因为数组序号不对,加了个0,或者用track by $index

@zeroone001
Copy link

@xufei 我刚尝试了一下,确实是在前面某个地方定义之后,在后面不需要加[],是可以的。第二点你说的数组序号不对,指的是数组第一个元素不能写成0吗?好像去掉0也不行

@xufei
Copy link
Owner Author

xufei commented Nov 19, 2015

@zeroone001 现在加上0了应该是对的啊,刚才你说的那个错误,是因为数组中会出现重复元素,索引失效。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants