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时代二三事 #21

Open
xufei opened this issue May 30, 2015 · 45 comments
Open

后Angular时代二三事 #21

xufei opened this issue May 30, 2015 · 45 comments

Comments

@xufei
Copy link
Owner

xufei commented May 30, 2015

后Angular时代二三事

JavaScript框架/库一直就是百花齐放,最近几年更是层出不穷。回顾这几年,有两个最引人注目的东西,一个是Angular,一个是React。其中,Angular最火的时间是2013年中到2014年末,React从2014年中开始升温,然后又由于ReactNative等周边项目,导致关注度很高。

2014年末,Angular官方宣布了一个大新闻,要完全重写Angular 2.0。这个事情让很多想要使用Angular的人止步不前,也给很多人带来了困惑。

随后,Angular 2.0的开发者之一创建了新的框架Aurelia,整体思路上与Angular相似,有一些细节的差异。那么,我们应当如何看待这些框架呢?

为什么Angular 2重写

如果不是有重大原因,没有哪个开发者会做出彻底重写,产生很多不兼容变更的决定。对于Angular来说,它面临这么一些原因:

  • Web标准的升级,主要是Web Components相关标准和ECMAScript的后续版本
  • 自身存在的一些问题:性能,模块,过于复杂的指令等等

使用转译语言

也正是Angular 2.0那篇大新闻,使大家知道了AtScript这样的语言,它在TypeScript的基础上添加了注解等功能。

有很多语言可以转译成JavaScript,比如CoffeeScript,Dart,TypeScript等,从最近的一些事件来看,TypeScript可以算是JavaScript转译领域的最大赢家。

很多人可能会有这样的疑问:为什么我们要用这些东西,而不是直接编写原生的JavaScript?开发语言的选择,很大程度上反映了我们对JavaScript组件化方案抽象度的需求。

比如说,Angular中,可以使用TypeScript来写业务代码,React中,通过JSX来使用组件,这都是具有较高抽象度的方案,能够让业务代码变得更直观。

ES6

先不看这些转译语言,来看看ES6,它给我们带来了很多编程的便利,每一次这种语言细节的升级,都引入了一些好用的东西,所以我们当然是期望尽早使用它。但问题是,浏览器的支持程度总是落后的,如果用它写了,在很多浏览器上不支持,比如箭头函数:

this.removeTodo = function(todo) {
    this.todos = this.todos.filter(item => todo!=item);
};

所幸,我们有Babel这样的转换器,可以把这样的代码翻译成ES5代码,它的生成结果就是

this.removeTodo = function(todo) {
    this.todos = this.todos.filter(function(item) {
        return todo != item;
    });
};

这个例子并不明显,如果你使用class之类的东西,就能体会到更大的改变。虽然说class这些只是语法糖,但用起来还是很爽的,可以复用一些传统的设计模式之类。

对于那些只需支持ES5+的项目而言,现在开始选用ES6语法编写代码是非常合适的,因为我们有Babel这样的东西,我们可以享受ES6新语法带来的愉悦编程体验,而无需承担兼容风险。

ES6新语法有很多,想要在生产过程中更好地使用,可以参见百度ecomfe的这篇使用ES6进行开发的思考

TypeScript

Angular和Aurelia都支持TypeScript,可以直接使用TypeScript编写业务代码。如果选用这样的框架,个人建议直接使用TypeScript。

为什么在类似Angular这样的体系里,我要建议使用TypeScript呢,因为这么几个原因:

长期的兼容性更好

很可能在现在这个阶段,你的项目还需要面对一些不支持ES6的浏览器,所以不能直接写ES6代码,但有可能有一天,浏览器支持了,但你的代码还是老的,它基本上还在使用ES5编写,想要迁移到ES6比较麻烦,以后每次迁移都是痛苦的过程。TypeScript就是以生成JavaScript为目标的,所以如果你用它写,只需选择生成参数,比如生成es5,es6就可以了,就算以后es继续升级,也只要改个参数就完事。

编写体验更好

TypeScript为代码提示作了很多特殊优化,比如:

ele.on("click", function(e) {
    // 这里我们是不知道e上面有什么,在编写的时候得不到提示
});

但是如果使用TypeScript编写,因为这个e的类型确定,所以就能有提示。

使用这样的语言也能够更快让非前端方向的人参与项目。

工作流程与管控

Angular的整体方案,由于分层很清晰,在JavaScript代码中基本就是纯逻辑,这样的代码如果使用TypeScript编写,会更加精炼,更加清晰。

这几年,大家逐渐接受了一个现实,那就是:前端也是需要构建的,所以我们有grunt,gulp这样的构建工具。之前我们不愿意写转译语言,是因为其他环节不需要构建,为了一些语法糖而引入整个构建环节代价太大。现在,既然发布之前的构建环节不可缺少,使用转译语言也不过就是加一段配置而已,这个使用代价已经小很多了。

Angular这样的解决方案,所面向的多数都是重量级产品,这些产品本身就会有构建环节,也基本上会使用IDE,所以,使用TypeScript的代价不大。

当项目变大的时候,我们会面临很大的管理成本,比如对代码的分析,结构调整,模块依赖关系梳理等,在TypeScript上面做,会比在JavaScript上面做更有优势。

最近几年前端领域“工程化”这个词被说得太多,但其实绝大部分说的都只是“工具化”。早在Visual Studio 2005中,就存在很多Factory插件,举例来说,一个普通项目的工作流程可能是这样:

  • 使用ER图设计模型结构
  • 一键生成数据库表结构和存取过程
  • 一键生成数据库访问层和实体定义代码
  • 一键生成Web Service接口
  • 根据WSDL,一键生成客户端的调用接口
  • 剩下的就是做界面,调用这些接口了

比如说我们做到一半,需要变更模型,也只是需要在ER图那边修改,然后依次一键变更过来。很多时候我们也会有代码的目录调整,批量更名,如果使用约束较强的语言,这部分可靠性会更高。

组件化与路由

如果用过angular 1.x,会对它的路由机制印象深刻。有复杂业务需求的人一般都不会使用内置的ng-route,而是会使用第三方的ui-router,这两者的核心差别是子路由的定义。

比如:

A界面有两个选项卡,分别B,C,如果我们想要:

app.html#a/b
app.html#a/c

这样的多级路由,在ng-route中想要定义,就比较麻烦,而在ui-router中,允许使用嵌套的ui-view指令,可以比较方便地支持这一功能。

在这两种方式下,路由都是全局配置的,但我们考虑在全组件化的场景下,组件的嵌套会受到这种路由配置的制约。比如,本来我们只是期望把某个组件嵌入到另外一个组件中,就能完成功能,但为了路由,不得不额外在全局路由配置的地方,加一个配置,而且每当组件层级发生变更的时候,这个配置都需要改,这就大幅拖累了我们组件体系的灵活度。

为此,我们可能会期望把路由配置放在每个组件中,比如说,组件A定义自己的路由为a,组件B的路由为b,组件C的路由为c,无需额外的配置,当B和C放在A中作为选项卡的时候,上面那两条路由会自动生效。

在Angular的新路由机制中,就是这样处理的,这也是Angular 2.0和Aurelia的共同路由机制。在这种机制下,如果有一天我们在另外一个更高层的组件D中,引入了组件A,那路由就会自己变成类似:

app.html#d/a/b
app.html#d/a/c

这个是非常灵活的,这对于我们构建一个全组件化的系统很有利,另外,这实际上实现了路由的动态配置。

当然,对这个问题,也是有争议的,因为路由不再集中配置,很难有一个地方能查看所有的路由状况了。

此外,由于在Angular 2和Aurelia中都凸显了组件的概念,组件的生命周期被引入了,比如说,组件的四个状态:

  • 创建前
  • 创建
  • 销毁前
  • 销毁

这些跟路由进行配合,可以把我们的加载过程,前置、后置条件过程都整理得很清楚。

指令与Web Components

最近,越来越多的人开始关注Web相关标准的推进,在HTML这个方面,最重要的标准就是Web Components,它主要是提供扩展HTML元素的能力(Custom Elements)。

HTML is great for declaring static documents, but it falters when we try to use it for declaring dynamic views in web-applications. AngularJS lets you extend HTML vocabulary for your application. The resulting environment is extraordinarily expressive, readable, and quick to develop.

这一段来自Angular的官方介绍。扩展HTML的词汇,是Angular的一种愿景,在这个里面,除了包含对元素的扩展,还有属性(Attribute)。

很多时候,仅仅有元素的扩展,是不足以满足需求的。举例说,让某个按钮闪烁,我们有两种方式实现:

  • 创建一种可以闪烁的按钮
  • 创建一种可以闪烁的行为

其中,前者是特定的解决方案,创建一个自定义元素<blink-button></blink-button>可以达到目的,但闪烁这个动作可以是一种通用行为,我们可能需要让图片闪烁,让链接闪烁,让各种元素都能闪烁,把这种行为扩展到不同的元素上。

如果用jQuery,我们可能会写:

$.fn.blink = function(options) {
    // 这里对DOM进行处理,添加闪烁功能
};

然后在使用的时候:

$('.some-element').blink();

如果说有自定义属性,可能我们就只要写:

<span blink>aaa</span>
<a blink>aaa</a>
<button blink>aaa</button>

借助数据绑定,还可以把blink绑定到一个变量上,由这个变量动态控制是否闪烁。

<div blink="hasNewMessage">aaa</div>

在Angular 1.x中,使用指令(directive)来实现自定义元素和自定义属性,这个东西设计得很复杂,所以不太容易上手,在2.0中,这一块改了。

在Angular 2和Aurelia中,使用很简单的标记来表明某个东西是自定义元素还是属性。

@customAttribute('blink')
@inject(Element)
export class Blink {
  element:any;

  constructor(element) {
    this.element = element;
  }
}
@customElement('my-calendar')
export class Calendar {
}

自定义属性的理念,在早期IE中实现的HTML Components中有很好的体现,它允许使用JavaScript编写DOM元素相关的代码,然后在css中作为行为附加到选择器上。

组件化与MVVM

对于大型Web应用来说,组件化是必须的,但是如何实现组件化,每个人都有自己的看法,所以组件化这个词就像民主,法制一样,容易谈,难做。

我们所期望的组件化往往是这样:

我们期望的组件化

但实际上,很可能是这样:

现实中的组件化

实际在用组件,尤其UI组件的时候,会出现很多尴尬的地方,比如说同一个组件在不同场景下形态不一致,所以我们需要多个层次的组件复用级别。

在Angular 1.x中,组件化并不是一个很明确的概念,它的整体思路还是:逻辑层+模板层这样的概念,此外,有一些指令(directive),用于表达对HTML标签、属性的增强。

在2.0版本中,组件成为了一个很清晰的东西。一个常见的组件,包含界面模板片段和逻辑类两个部分。

如果我们经历过Angular 1.2之前的版本,可能会感受到controller的一些变化。比如说,之前我们写一个controller,可能是:

function TestCtrl($scope) {
    $scope.counter = 0;

    $scope.inc = function() {
        $scope.counter++;
    };
}

然后这样用:

<div ng-controller="TestCtrl">
    {{counter}}
    <button ng-click="inc()">+1</button>
</div>

在1.2之后,我们会这样写:

function TestCtrl() {
    this.counter = 0;

    this.inc = function() {
        this.counter++;
    };
}

然后这样用:

<div ng-controller="TestCtrl as test">
    {{test.counter}}
    <button ng-click="test.inc()">+1</button>
</div>

注意TestCtrl的实现,里面没有$scope了,这意味着什么呢?意味着这个“controller”已经不再是controller了,而是view model,这个部分的代码变得更加纯净,每有一个对应的界面片,就实例化一个出来与之对应绑定。

在Angular 2和Aurelia里面,HTML模板与视图模型被视为一体,当做一个组件,而Aurelia的灵活度更高,因为它尽可能地把额外的配置放在HTML模板中,所以视图模型变得更单纯,也存在复用价值了。

Aurelia跟Angular 2有不少细节差异,写法上大致的对比可以从这里看出:Porting an Angular 2.0 App to Aurelia

Angular支持使用pojo作为数据模型,这可以算是它的优点之一,这样,它对模型层的定义就比BackBone和Knockout简洁很多。

但是在2.0时代,我个人是倾向于预定义模型类型的,因为在MVVM这三层中,不宜过于淡化VM和M的分界,分清哪些东西是从属于模型的,哪些东西是从属于视图模型,在很多情况下都会很重要。这会影响我们另外一些工程策略,比如测试环节的处理方式。

在大型应用中,model应当与store视为一体,在比如数据的共享,缓存,防冲突,防脏等方面综合考虑,而view model可以不要考虑得这么复杂。

基于MVVM,我们可以在不同层级复用组件,可以把模板和视图模型当做一个整体复用,也可以只复用视图模型,使用不同的模板。在这一点上,Angular 2显然比Aurelia欠考虑。

代码的迁移

Angular的这次升级,最令人不满的是它的不兼容变更。这些变更很多方面来说,是无奈之举,因为前后的差距确实有那么大,想要短期平滑,就得在未来背负更重的历史负担。

但事实上,我们在很多场景下,比如企业应用领域,并没有比它更好的解决方案,所以这时候需要来看看如果想要作一个版本迁移,需要做哪些事情。

如果我们要做从Angular 1.x到2.0的代码迁移,相对最容易,也最值得做迁移的部分是数据模型,但这个问题说难也难,说简单也简单。

很多对分层理解不深的人,很可能把这个代码迁移想得过于复杂。但其实,一个规划良好的Angular 1.x工程,它的代码结构应该是非常有序的,什么东西放在模板里,什么东西放在controller,service,都是非常清楚的,而且,绝大多数controller和service中,是不应有DOM相关的代码的。

比如,service中是什么?主要是数据模型的存取,与服务端的交互,本地缓存,公共方法等,这些东西要迁移到2.0中,是很容易的,只是写法会稍有差别。

接下来往上看看,看这个所谓的controller。在2.0中,不再有controller,service这些东西的区分,一切都是普通的ES类,但是理念还是有的。比如一个含有视图的组件,它的逻辑部分就会是一个ES类,这个也就是视图模型,基本上也就对等于1.x中的controller。

比如最简单的todo:

function TodosCtrl() {
    this.todos = [];
    this.newTodo = {};

    this.addTodo = function() {
        this.todos.push(this.newTodo);
        this.newTodo = {};
    };

    this.removeTodo = function(todo) {
        this.todos = this.todos.filter(function(item) {
            return item != todo;
        });
    };

    this.remainingCount = function() {
        return this.todos.filter(function(item) {
            return item.finished;
        }).length;
    };
}

这代码很简单,就是给一个列表添加移除东西,假设我们要把这个代码移植到2.0,可以说基本没有代价,因为在2.0里你要实现这样的功能,也得这么写。

(注意,下面这段是Aurelia代码,并且不是使用ES6,而是使用TypeScript编写)

export class Todos {
    public todos: Array<Object> = [];
    public newTodo: Object = {};

    addTodo(): void {
        this.todos.push(this.newTodo);
        this.newTodo = { content: "" };
    }

    removeTodo(todo): void {
        this.todos = this.todos.filter(item => todo != item);
    }

    get remainingCount() {
        return this.todos.filter(item => item["finished"]).length;
    }
}

这么一看,好像也很容易迁移过去,多数情况下是这样,但这里面有坑。坑在什么地方呢?主要是手动添加变更检测的部分。变更检测是个复杂的话题,在本文中先不讲,后面专门写一篇来讲。

现在我们把逻辑层摆平了,来看界面层,这里主要有三个东西,一个是原先的指令,一个是普通的模板,还有一个是过滤器。

指令的问题好办,我们刚才提到的自定义元素,自定义属性,其实对使用者是没什么差别的,也就是实现的人要把代码迁移一下。

我个人并不赞同在一个业务型的项目中封装太多自定义元素,仅仅那种被称为“控件”的东西才有这个必要,其他东西可以直接采用模板加视图模型的方式,具体理由在前一篇的组件化之路中提到过。如果是按照这种理念去实现的业务项目,指令这块迁移成本也不算高。

过滤器也很好办,2.0 有同样类似的机制实现。

普通模板这边,绝大部分都是固定的工作量,比如ng-repeat,ng-click换个写法而已,里面有一些影响,但基本上是可以用批量转换去搞定的。

所以我们发现,迁移的成本并没有想象的那么大,为了更好地拥抱Web标准和更好的性能,这样的事情是比较值得去做的。

Angular与React

这两种东西代表着现代Web前端的两种方法论,前者是以分层和绑定为核心的大一统框架,后者提供了渲染模型多样化,带生命周期的多层组件机制。由于实现理念的不同,用它们分别开发同样的Web应用也会有很大差异。好比我们造一个仿生机器人,用Angular是先造完骨架,把基本运动功能调试完,然后加装肌肉等部件,最后贴皮肤,眉毛,头发,指甲;用React是先造出各种器官,肢体,然后再拼装。

方法论的事情那个很难说对错,只有看场景。比如亚洲农民跟美洲农民种地,理念肯定是不同的,因为他们面临的场景不同,比如亚洲种地普遍很精细化,美国种地很粗放。这也有些像React和Angular的差别。

我个人不赞同在框架的问题上有太多争论,因为天下武功,到底什么厉害,完全是看人的,一阳指在段正淳手里,只能算二流,到了南帝段智兴手里,可与降龙十八掌齐名。聚贤庄一战,乔帮主用最普通的太祖长拳,打得天下英雄落花流水。如果深刻理解了一个技术的优点和缺点所在,扬长避短,则无往而不利。

近年来,各框架是在互相学习的过程,但是每个东西到底有什么不同,最好还是列出需求,分别用代码体现。现在已经有todomvc这么一个库,用各种框架实现todo,但在我看来,这个需求还太小,不足以表达各自的优势。

我倡议,每个框架的熟练使用者能够选出一些典型场景,然后写一些demo,供更多的人学习对比之用。

Angular与未来

到目前为止,我们在浏览器中看到系统从规模来说都是中小型的,与传统桌面的大型软件们相比,还很幼小。比如Office的开发团队,千人以上的规模,无论是代码的架构,还是人员的分工协作,都可以算是伟大的工程。

在大型系统中,组件化可以说是立足的基础,但怎样去实践组件化的思想,是一个见仁见智的话题。

还是以Office为例,它除了提供图形化的操作界面,还提供了一套API,可以被VBA这样的嵌入语言调用。

比如说,我们可以在界面上选中一个工作表,然后在某行某列填入数据,也可以在VBA中使用这样的语句去达到同样的目的

这就意味着,对于同一种操作,存在多样化的外围接口。继续分析下去,我们会发现,存在一种叫做Office Object Model的东西,这也就是一个核心数据模型,我们所有的操作其实都是体现在这个模型上的,GUI和VBA分别是这个模型的两个外围表现。

所以可以想象,如果Office的测试团队想要测试功能是否正确,他是有两条路要走:

  • 通过VBA这么一个相对简单直接的方式,去调用OOM上的方法和属性,然后再次通过VBA去验证结果
  • 通过GUI上类似录屏的操作,去模拟人的一些操作,然后,通过VBA或者是界面选取的方式验证结果

从这里可以大致感受到,当系统越复杂的时候,独立的模型层越重要,因为必须保持这一层的绝对清晰,才能确保整个系统是正确而稳定的。层层叠加,单向依赖,这使得软件正确性的验证过程变得更加可控。

在业务系统中,又存在另外一些问题。以我曾经从事过的电信行业软件系统为例,整个运营与业务支撑系统由若干个子系统构成,比如:

  • 资源管理,管理卡、号、线等资源
  • 营业系统,负责对外营业
  • 计费与结算
  • 运维与调度,负责人员权限考核调度等
  • 相关的内部管理系统

这些系统基本都已经Web化,如果我们要探讨它们的组件化方式,必须作相当深远的考虑,因为,还可能出现终极杀手——比如呼叫中心系统。

大家打客服电话的时候,有没有注意到,客服人员可以操作的东西,是超过了前台营业员的,这也就说明他实际上能够操作以上某几个系统。可是我们也没有发现他在切换多种功能的时候,花太多时间,说明其实他有一个高度集成的界面入口。

这就来了问题了,如果这里的多数功能是集成其他系统的组件所致,那都该是一些什么样的组件啊?

小结

篇幅所限,不在本文中讨论这些问题。抛出这样的问题来,是为了让大家察觉,在很多不为人知的地方,存在很值得思考的东西。一些新的Web标准是为了解决Web系统的大型化,应用化,但仅仅以这些标准本身而言,还是存在一定的不足,需要更深刻的改变。

我们期望Angular2和Aurelia为代表的新型框架能够给这些领域带来一些灵感,互相碰撞,解放更多人的生产力。

总而言之:

“I think we agree, the past is over.” – George W. Bush

@xufei
Copy link
Owner Author

xufei commented May 30, 2015

这篇是为Segmentfault 3周年活动前端场准备的,有些仓促,因为最近太忙了……demo稍后整理

@titanew
Copy link

titanew commented May 30, 2015

nice shot!

最近在做一个内部的叫筋斗云的项目,第一次真正在项目中用angular. 于jq相比,第一次使用angular更多的是一种思维的转变,思维转变了接下来就易懂的多...

@liuweifeng
Copy link

good job ! 👍

@luqin
Copy link

luqin commented May 30, 2015

👍

@fengqiyue
Copy link

thanks

@unbug
Copy link

unbug commented May 30, 2015


有思考就有收获

@iqingting
Copy link

👍

@JavaPythonGO
Copy link

thx!学习

@atian25
Copy link

atian25 commented May 30, 2015

最近都在搞scrat, 没关注ng, 看起来ng2的语法似乎稳定了,下个月抽时间可以考虑看看ng2

@tiye
Copy link

tiye commented May 30, 2015

期待 Angular 2 和 TypeScript 方面的进展.

关于 React 的对比, 最近注意到一个点, 就是当我们后端工程师跟我讨论组件的时候,
他熟悉 Rails 和 Node 方面的做法, 对组件的理解应该说是更接近面向对象,
比如说一个 field 内容封装组件, 就暴露方法获取和改变 field 具体数据..
跟他讨论过程中我意识到 React 完全不同, 不赞成从外界获取和操作组件内部数据了
而是传给组件属性, 传给组件修改属性的方法.. 这个思路跟以往的大不相同
我对 Angular 当中做法不够明确. 不过这个套路和 jQuery 时代已经完全不一样了

@leeluolee
Copy link

关于引入编译流程

这个毫无疑问,编译这一环节现在肯定都会有, 关键是在开发阶段 “是不是要引入发布级”的编译. 比如我们现在其实也会实用browserify + commonjs 来编写较小的单页应用(4人-的项目), 但是大型工程还是会选择AMD 的方式。 两方面考虑。

  • 构建成本,时间真的应该是考虑因素了
  • 团队成员的学习成本
  • 我觉得是工具本身就应该独立出框架, 而不是没有工具就没法使用这套框架了, 毕竟现在IDE很弱,大部分还是得靠命令行

##关于React 和 Angular

我和飞哥的意见很一致, 入门而言肯定是模板型的更容易入门, vd对于用户是透明的,然而diff却发生在这一层。 但传统mvvm的数据却是用户真实可触碰。 这两者的使用成本不言而喻。 关键是我越来越觉得或者怀疑的是:

其实现在很多鼓吹React的人, 根本就没有尝试过传统的mvvm方案。站队、押宝性质的去接触这个社区, 满嘴一些高级术语和词汇

Angular 和 React 带来的理念都是 颠覆性的, 我觉得两者后面肯定会共存下去, 使用场景根本不是谁取代谁的冲突

关于webcomponent

最大的优势是生命周期, 这个我在昨天会议中也和你提到了, polymer 或许可以绕过, 但是custom element是绕不过的。 好在完全组件化抽象后的框架输出的内容其实很容易注册成一个custom element. 比如regularjs的一个实验性扩展,就几十行代码 https://github.com/regularjs/customelements-register ,这个angular1.0不好处理, 但是2.0 应该也容易做到。

关于路由

ng-route肯定是不够用的, 这个我同意, 关于new-router倒是后续可以仔细关注下。

集中式和动态注册,其实各有优劣, 组件本身不涉及路由配置,你就可以让这个组件参与到多个路由节点, 最好的办法是用户自主选择, 比如我之前的stateman 其实就同时支持动态注册和集中式管理,

还有一个昨天你有个点, 貌似不在文章里

就是组件本身不适合过度封装? 还给了小马的尾巴的例子。

其实我很赞同了, 因为组件和模块不同的地方是, 组件的模板(或view)是有结构的, 而模块暴露的接口是可以适度重复的(你给的例子其实更适合用来说明模块,有重复但是仍然可用),但是组件不一样,结构只要有细微的不满足, 那整套模板就是不可用的。

所以我们可以安全封装可以冗余的业务逻辑(或vm上的方法)继承下来,因为不涉及到view, 更接近模块的定义, 比如我们内部的ListView其实也是没有template结构的, 而只有业务逻辑封装, 因为模板的变化太大了。

关于模板的封装, 我想到的有两种方式
一种方式是 include, 这个也是你提前定义好哪些可能会被替换, 比如一个modal弹窗的内容区, 显然应该留好这个placeholder
第二种方式可以直接微调模板, 这个有完整parse流程的regularjs就有优势了, 我可以提供hook给用户,直接操作结构化后的AST. Angularjs这种就很难做

结尾

写的有些凌乱, 也算一个记录。

未完待续...

@xufei
Copy link
Owner Author

xufei commented Jun 1, 2015

@jiyinyiyong angular也不赞成从外部对组件进行操作,但web components这个东西的理念是跟他的理解很接近的。所以,不管react还是angular,都会在web components上面再来一次抽象,隔离开发者对它的直接操作。

polymer应该更适合你同事的理解

@xufei
Copy link
Owner Author

xufei commented Jun 1, 2015

@leeluolee 有几段文字和插图没补上,刚加上了

@kakanjau
Copy link

kakanjau commented Jun 1, 2015

飞哥,赞!

@markyun
Copy link

markyun commented Jun 1, 2015

image

@SiZapPaaiGwat
Copy link

这图片很费心思啊 😅

@huangtengfei
Copy link

学习学习~

@RainZhai
Copy link

RainZhai commented Jun 1, 2015

TypeScript 用起来有java的语法特点,但是貌似多态搞不了,还有很多待了解

@sandwich99
Copy link

对于大型Web应用来说,组件化是必须的,但是如何实现组件化,每个人都有自己的看法,所以组件化这个词就像民主,法制一样,容易谈,难做。

实际在用组件,尤其UI组件的时候,会出现很多尴尬的地方,比如说同一个组件在不同场景下形态不一致,所以我们需要多个层次的组件复用级别。

在Angular 1.x中,组件化并不是一个很明确的概念,它的整体思路还是:逻辑层+模板层这样的概念,此外,有一些指令(directive),用于表达对HTML标签、属性的增强。

组件化

实际开发中, 合理规划组件的粒度十分困难。 必须承认, 我们团队开发的组件整体质量还不高, 大家都在摸索中 。

但由于分工css 与 js 岗位职责的分离 ,这些质量参差不齐的组件,在缺少文档, 测试, 一致的命名规范的情况下, 为CSS 样式的工程化管理带来很大的挑战。 与此同时, 在团队合作中, 还有产品经理、 UI 后端等各个岗位的配合上也存在问题。

可以说, web component 还不成熟。 无论是技术上 还是 人才上。

在新旧交替之际, 怎么让前端开发拥抱新技术。 同时保证让团队各个环节合作顺畅, 项目稳步前进。 是我们值得思考的, 以下是我在angular1.x 中得实践经验:
  1. 减少组件的状态,子组件嵌套的深度不超过2层。
  2. 减少路由的状态, 不要过度使用ui-router, 尽量使用单路由, 嵌套深度不超过2层。
  3. 将controller与路由写在一起, 因为路由的所依赖的数据和实际业务无法分离(在controller中加载数据会导致页面闪烁,同时route 成功 hook 不正确。 同时导致应用有状态, 导致用户无法F5等等)。 综合权衡只有舍弃路由的全局配置管理, 将路由和业务层代码放在一起。(和angular2不谋而合)
  4. 为了规避1、2 损失的灵活性。 partial采用模板引擎管理(jade), 使用 extend include conditionals 等模板语言特性。 在构建期间生成真正angular使用的模板 。 这样提高性能, 增加复用性, 使用原生标签布局,减少学习、沟通成本。 进一步隔离样式 与 组件的, CSS 与 JS组件, 设计人员 与 系统业务
  5. directive, 使用动态 directive ; directive 中使用lodash template 模板预编译; 合理使用 compile 配置,在angular程序运行的不同周期, 动态改变模板。 减少不必要的双向绑定, 优化性能, 增加灵活性、复用性, 降低学习成本。

关于angular2

angular2 彻底摒弃了 angular1 中过于复杂概念, 拥抱标准。 更容易的总结出一套一致的最佳实践

同时使用typescript , 类型定义更清晰, 对IDE 更友好, 写代码应该更容易, 更健壮

期待。

@nwind
Copy link

nwind commented Jun 1, 2015

去年尝试过 TypeScript,随着项目变大编译时间越来越慢,有时候甚至要等 2 分钟,加上编译检查过于严格,好多代码都不知道怎么写了,所以真正用起来的体验是超级不爽。。。

@luqin
Copy link

luqin commented Jun 1, 2015

现在的前端流行的做法都需要编译,但是编译会随着代码量增加而越来越慢。项目大之后如何分模块开发又是一个问题了。
目前正在开发一个超大型单页应用,重度交互应用。遵循commonJS规范开发,使用babel编译ES6语法,webpack按需加载js进行分包。现在的初次编译已达到2.5min,随着项目的变大,编译的问题也急需解决。不知道大家有什么好的方法。

@xufei
Copy link
Owner Author

xufei commented Jun 1, 2015

@nwind 项目变大这个问题不是很大,因为如果用它写全项目,我建议的方式是先规划后编码,引用的时候,把已确定的东西都先搞成类型文件,那部分的东西单独构建,这样,当某个人写自己模块的时候,依赖的东西基本都不需要编译,所以不至于特别慢。

至于说编译检查过于严格这事,确实带来不少学习成本,但我觉得如果纯逻辑代码的话,还好啊,在ng2或者aurelia的场景里,只用来写model和view model,应该不会这么可怕。

@xufei
Copy link
Owner Author

xufei commented Jun 1, 2015

@luqin 编译时间应该也可以算ts比es6的优势之一,因为可以引入预先定义的d文件,所以被依赖的东西不必每次都编译,每个业务开发者其实只是在编译自己的部分。

@Tonylvv
Copy link

Tonylvv commented Jun 2, 2015

学习!!

@yyx990803
Copy link

居然不提到 Vue,差评(逃

@xufei
Copy link
Owner Author

xufei commented Jun 2, 2015

@yyx990803 不熟悉的东西我实在不敢说。。。

@MrHuxu
Copy link

MrHuxu commented Jun 2, 2015

@xufei 我是一个Angular新手,看了这篇文章感觉获益良多。刚刚在看了Angular2的文档后,我有一些疑问:

  1. 我看到Angular2提供了ComponentView,看上去有点像React利用JSX创建virtual dom然后render到real element的方式(也许只是看上去像...),请问在Angular2中这样得到的组件是存在于内存中还是会成为页面上的一个real dom呢?
  2. 如果是内存中virtual dom,那么可以理解更新视图有性能提高,如果是渲染成一个real dom,那么Angular2是怎么优化dirty-check之后更新视图的操作呢?

我的见解可能比较浅薄,但是仍然希望您能给我解答一下

@cycle263
Copy link

cycle263 commented Jun 3, 2015

11年开始使用angularjs,刚开始被它的双向绑定、路由、指令等功能吸引,后来随着项目越来越庞大,性能方面存在一定的问题,想尽各种方式进行优化,却不得其果。
angular2和react在性能方面的确有很大优势,目前正在考虑迁移过来

@joeylin
Copy link

joeylin commented Jun 4, 2015

angularjs 对百度的seo,一般用什么思路来实现呢?

@MrHuxu
Copy link

MrHuxu commented Jun 4, 2015

@joeylin 尝试一下Prerender,预渲染页面让搜索引擎来爬取和收录

@ovaldi
Copy link

ovaldi commented Jun 4, 2015

希望来一期React,:)

@just4fun
Copy link

just4fun commented Jun 9, 2015

之前一直用Angular,现在突然宣布2.0不向后兼容,Angular应该掉了很多粉。
现在个人比较倾向React这种职责单一的玩意儿,以后就算React新版本也不向后兼容,升级甚至替换的代价也要小很多。

@joeylin
Copy link

joeylin commented Jun 15, 2015

angular使用中,我遇到一个问题,我$watch('width')中修改height,$watch('height')中修改width, 最终产生一个错误, 大家遇到这种需求的时候,一般是怎么解决的呢?

@xufei
Copy link
Owner Author

xufei commented Jun 15, 2015

@joeylin 只要是在几步操作之内能收敛就行了,不能一直变下去。

比如说,你在width里面写:height = width * 2;
在height里面写:width = height / 2;

这样,无论是对哪个赋值,都能很快收敛回来,两个式子的结果都不再变化,尽管效率未必高,但结果是可以了。

你什么需求会这么写啊,详细说说,感觉逻辑没有理清楚……

@joeylin
Copy link

joeylin commented Jun 15, 2015

@xufei 我想让一个div的宽高,强制按比例缩放,比如增大width,height也同时增大,增大height,width也能同时按相应的比例增大。

@joeylin
Copy link

joeylin commented Jun 15, 2015

@xufei 对,这样的值,正常是可以的,我担心的是,在计算的时候值出现一些微小得差异,最终造成,无法收敛。

@benjycui
Copy link

@joeylin Math.round? 1px内的误差应该可以接受。

@usherwong
Copy link

赞,写的真好

@shikelong
Copy link

学习了

@Fiery
Copy link

Fiery commented Sep 30, 2015

很多很好的思考啊,学习了。
关于Office Object Model这部分想法很形象,确实可以和Web类比,不过感觉应该类比的是DOM而不是MV*框架的Model层吧,毕竟除了Backbone其它主流框架的Model其实都没有很heavy。

另外最近很关注React带来的一些新思想,然后看了你前几篇关于Angular的讨论,也理解了你对于组件化的意见,就像你在这篇文章最后suggest的那样,组件化的问题就在于复用带来的效率提升和组件构建成本之间的tradeoff,实际上是一个很难的决定。结合之前你在 MVVM时代的Web控件 ——基于AngularJS实现 里面提到的复用Display Logic和相关的一些实践,我想大概梳理一下我的想法。

感觉上对于UI(特指View)来讲,复用性已经不太是关注点了,因为不管是Layout还是Display Logic的复用性都不大,像Accordion算是一个,但实际上因为这个logic太基本,是不是值得复用也不好说(相较牺牲这种复用性而compose大一些的组件),而对于稍复杂一点的UI来说,复杂的state mutation和layout如果works as expectation还好,一旦出问题就比较抓狂,所以想来想去,还是可维护性最重要,不如把相关model和logic都放在一起方便debugging和testing。由此带来的潜在福利应该是很可观的。尽管好多人说MVVM这种对小型项目来说开发效率更高,但是我觉得其实就是遵循了传统后端的web架构思想,大家已经熟悉并且接受,写起来没有思想负担而已,真要说MVVM模式是否合适,我觉得还是要抱开放一点的心态,跳出套路进行比较。

比如PeteHunt在13JSConf上讲的就是Separation of technologies != Separation of concerns,最早说要把HTML/CSS和JS分离,最重要的是Design和Logic分离,这在web1.0时代是非常有效的,因为当时的static web还主要只是Layout和Style,不需要任何逻辑来支持动态效果。而Templating也是在这个大背景下,从后端渲染的MVC模式直接借鉴到前端的。不过在JS已经是前端主要语言,前端UI所需要的Display Logic的代码量非常庞大的现在,我们的UI已经无法避免要绑定几套不同的显示状态,还要带上underlying的logic handler,而这种时候还要试图分离HTML/CSS和JS就会很难受,MVVM里的VM做了大量后台的工作简化了data和display logic和view的binding,已经做到了declarative HTML as View,但是如果离开JS单看这个HTML template其实就有点无所适从,因为directives的behavior并无标准可循,要track back到JS那边才能完全理解。所以在Against CSS in JS里面KG会说

the mantra of React is to stop pretending the DOM and the JavaScript that controls it are separate concerns

这么想的话,React选择的激进的组件化风格就还是很有其现实意义的。而反思templating的思想包袱的话,就是一定要强调HTML和JS分离,相反的结合JSX的话Design完全有能力对组件进行修改,这里就需要我们有一个open mind,接受Engineer和Designer可以是在相同环境中工作的可能性,这个时代,连我们的Designer都会一些JS了,与其让他在一个复杂的cascaded逻辑链或者CSS套用中去做改变,还不如让他在JSX中安心修改单个组件而不用担心side effect要好吧。不过这确实和公司的实力有关系,我认为FB有能力做更好的training给designer所以才能完全把React思想执行下去吧。

React最重要的两个思想就是VirtualDOM和JSX(embed markup/CSS in JS),Virtual DOM各大框架都可以adopt,而且个人觉得是React更重要更有价值的部分,但是第二点就和现在流行的MV_框架走的是不同的路,也许是时候考虑一下这种完全革新的架构了,React的设计者们从一开始就只关注UI,把UI作为driving force,才能够跳出一般的思路而把VIew单独拿出来进行思考,所以才能找到一种让开发更有效率的框架实现,而另一边,籍着这种disruptive的思路,Flux也可以跳出MV_的two-way binding的老路,提出一种完全不一样的pattern,让人耳目一新。个人认为也许Flux+React这种看似颠覆式的实现是next gen web的一个信号也说不定。

@zhanghuanchong
Copy link

写的真好,赞! 👍

@SuperChrisliu
Copy link

版本已经迭代到rc4,那么现在新项目是时候选择angular2了?

@sc-56
Copy link

sc-56 commented Dec 31, 2023

赞,很棒的。

@dongbruno
Copy link

dongbruno commented Dec 31, 2023 via email

@sumili
Copy link

sumili commented Dec 31, 2023 via email

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