Angular2新人常犯的5个错误
看到这儿,我猜你肯定已经看过一些博客、技术大会录像了,现在应该已经准备好踏上angular2这条不归路了吧!那么上路后,哪些东西是我们需要知道的?
下面就是一些新手常见错误汇总,当你要开始自己的angular2旅程时,尽量避免吧。
注:本文中,我假设诸位已经对angular2的基础知识有所了解。如果你是绝对新手,之前只听说过,完全没概念什么是angular2的,先去读读下面这些资料:
错误 #1:原生hidden属性绑定数据
在AngularJS 1中,如果想切换DOM元素的显示状态,估计你会用AngularJS 1内置的指令如:ng-show 或者 ng-hide:
AngularJS 1示例:
<div ng-show="showGreeting">
Hello, there!
</div>
而angular2里,新的模版语法允许你将表达式绑定到DOM元素的任何原生属性上。 这个绝对牛逼的功能带来了无限的可能。其中一项就是绑定表达式到原生的hidden属性上,和ng-show有点像,也是为元素设置display: none:
angular2的[hidden]示例(不推荐):
<div [hidden]="!showGreeting">
Hello, there!
</div>
第一眼看上面的例子,似乎就是AngularJS 1里的ng-show。其实不然,她们有着!important的不同。
ng-show和ng-hide都是通过一个叫ng-hide的CSS class来控制DOM元素的显示状态,ng-hide class就是简单的把元素设置成display: none。这里的关键在于,AngularJS 1在ng-hide class里增加了!important,用来调整该class的优先级,使得它能够覆盖来自其他样式对该元素display属性的赋值。
再来说回本例,原生hidden属性上的display: none样式是由浏览器实现的。大多数浏览器是不会用!important来调整其优先级的。因此,通过[hidden]="expression"来控制元素显示状态就很容易意外的被其他样式覆盖掉。举个例子:如果我在其他地方对这个元素写了这样一个样式display: flex,这就比原生hidden属性的优先级高(看这里)。
基于这个原因,我们通常使用*ngIf切换元素存在状态来完成相同目标:
angular2的*ngIf示例(推荐):
<div *ngIf="showGreeting">
Hello, there!
</div>
和原生hidden属性不同,angular2中的*ngIf不受样式约束。无论你写了什么样的CSS,她都是安全的。但还是有必要提一下,*ngIf并不是控制元素的显示状态,而是直接通过从模版中增加/删除元素该元素来达成显示与否这一效果的。
当然你也可以通过全局的样式给元素的hidden属性增加隐藏的优先级,譬如:display: none !important,来达到这个效果。你或许会问,既然angular小组都知道这些问题,那干嘛不在框架里直接给hidden加一个全局最高优先级的隐藏样式呢?答案是我们没法保证加全局样式对所有应用来说都是最佳选择。因为这种方式其实破坏了那些依赖原生hidden能力的功能,所以我们把选择权交给工程师。
错误 #2:直接调用DOM APIs
只有极少的情况需要直接操作DOM。angular2提供了一系列牛X的高阶APIs来完成你期望的DOM操作,例如:queries。利用angular2提供的这些APIs有如下优势:
单元测试里不直接操作
DOM可以降低测试复杂度,使你的测试用例跑的更快把你的代码从浏览器中解藕,允许你在任何渲染环境里跑你的程序,譬如:
web worker,或者完全离开浏览器(比如:运行在服务器端,亦或是Electron里)
当你手动操作DOM时,就失去了上述优势,而且代码越写越不易读。
从AngularJS 1(或者压根没写过Angular的人)转型的朋友,我能猜到大概哪些场景是你们想直接操作DOM的。那我们来一起看下这些状况,我来演示下如何用queries重构她们。
场景 一:当需要获取当前组件模版里的某一个元素时
假设你的组件模版里有一个input标签,并且你希望在组件加载后立即让这个input自动获取焦点
你或许已经知道通过@ViewChild/@ViewChildren这两个queries可以获取当前组件模版里的内嵌组件。但在这个例子里,你需要的是获取一个普通的HTML元素,而非一个组件。一开始估计你就直接注入ElementRef来操作了:
直接操作ElementRef(不推荐)
@Component({
selector: 'my-comp',
template: `
<input type="text" />
<div> Some other content </div>
`
})
export class MyComp {
constructor(el: ElementRef) {
el.nativeElement.querySelector('input').focus();
}
}
其实我想说的是,这种做法没必要。
解决方案:@ViewChild配合local template variable
程序员们没想到的是除了组件本身,其他原生元素也是可以通过local variable获取的。在写组件时,我们可以直接在组件模版里给这个input标签加标记(譬如:#myInput), 然后把标记传给@ViewChild用来获取该元素。当组件初始化后,你就可以通过renderer在这个input标签上执行focus方法了。
@ViewChild配合local variable(推荐)
@Component({
selector: 'my-comp',
template: `
<input #myInput type="text" />
<div> Some other content </div>
`
})
export class MyComp implements AfterViewInit {
@ViewChild('myInput') input: ElementRef;
constructor(private renderer: Renderer) {}
ngAfterViewInit() {
this.renderer.invokeElementMethod(this.input.nativeElement,
'focus');
}
}
场景 二:当需要获取用户映射到组件里的某个元素时
如果你想获取的元素不在你的组件模版定义里怎么办?举个例子,假设你有个列表组件,允许用户自定义各列表项,然后你想跟踪列表项的数量。
当然你可以用@ContentChildren来获取组件里的“内容”(那些用户自定义,然后映射到你组件里的内容),但因为这些内容可以是任意值,所以是没办法向刚才那样通过local variable来追踪她们的。
一种方法是,要求用户给他将要映射的列表项都加上预定义的local variable。这样的话,代码可以从上面例子改成这样:
@ContentChildren和local variable(不推荐)
// user code
<my-list>
<li *ngFor="#item of items" #list-item> {{item}} </li>
</my-list>
// component code
@Component({
selector: 'my-list',
template: `
<ul>
<ng-content></ng-content>
</ul>
`
})
export class MyList implements AfterContentInit {
@ContentChildren('list-item') items: QueryList<ElementRef>;
ngAfterContentInit() {
// do something with list items
}
}
可是,这需要用户写些额外的内容(#list-item),真心不怎么优雅!你可能希望用户就只写<li>标签,不要什么#list-item属性,那肿么办?
解决方案:@ContentChildren配合li选择器指令
介绍一个好方案,用@Directive装饰器,配合他的selector功能。定义一个能查找/选择<li>元素的指令,然后用@ContentChildren过滤用户映射进当前组件里的内容,只留下符合条件的li元素。
@ContentChildren配合@Directive(推荐)
// user code
<my-list>
<li *ngFor="#item of items"> {{item}} </li>
</my-list>
@Directive({ selector: 'li' })
export class ListItem {}
// component code
@Component({
selector: 'my-list'
})
export class MyList implements AfterContentInit {
@ContentChildren(ListItem) items: QueryList<ListItem>;
ngAfterContentInit() {
// do something with list items
}
}
注:看起来只能选择<my-list>里的li元素(例如:my-list li),需要注意的是,目前angular2尚不支持"parent-child"模式的选择器。如果需要获取组件里的元素,用@ViewChildren、 @ContentChildren这类queries是最好的选择
错误 #3:在构造器里使用获取的元素
第一次使用queries时,很容易犯这样的错:
在构造器里打印query的结果(错误)
@Component({...})
export class MyComp {
@ViewChild(SomeDir) someDir: SomeDir;
constructor() {
console.log(this.someDir);// undefined
}
}
当看到打印出来undefined后,你或许以为你的query压根不能用,或者是不是构造器哪里错了。事实上,你就是用数据用的太早了。必须要注意的是,query的结果集在组件构造时是不能用的。
幸运的是,angular2提供了一种新的生命周期管理钩子,可以非常轻松的帮你理清楚各类query什么时候是可用的。
如果在用view query(譬如:
@ViewChild,@ViewChildren)时,结果集在视图初始化后可用。可以用ngAfterViewInit钩子如果在用content query(譬如:
@ContentChild,@ContentChildren)时,结果集在内容初始化后可用。可以用ngAfterContentInit钩子
来动手改一下上面的例子吧:
在ngAfterViewInit里打印query结果集(推荐)
@Component({...})
export class MyComp implements AfterViewInit {
@ViewChild(SomeDir) someDir: SomeDir;
ngAfterViewInit() {
console.log(this.someDir);// SomeDir {...}
}
}
错误 #4:用ngOnChanges侦测query结果集的变化
在AngularJS 1里,如果想要监听一个数据的变化,需要设置一个$scope.$watch, 然后在每次digest cycle里手动判断数据变了没。在angular2里,ngOnChanges钩子把这个过程变得异常简单。只要你在组件里定义了ngOnChanges方法,在输入数据发生变化时该方法就会被自动调用。这超屌的!
不过需要注意的是,ngOnChanges当且仅当组件输入数据变化时被调用,“输入数据”指的是通过@Input装饰器显式指定的那些数据。如果是@ViewChildren, @ContentChildren的结果集增加/删除了数据,ngOnChanges是不会被调用的。
如果你希望在query结果集变化时收到通知,那不能用ngOnChanges。应该通过query结果集的changes属性订阅其内置的observable。只要你在正确的钩子里订阅成功了(不是构造器里),当结果集变化时,你就会收到通知。
举例,代码应该是这个样子的:
通过changes订阅observable,监听query结果集变化(推荐)
@Component({ selector: 'my-list' })
export class MyList implements AfterContentInit {
@ContentChildren(ListItem) items: QueryList<ListItem>;
ngAfterContentInit() {
this.items.changes.subscribe(() => {
// will be called every time an item is added/removed
});
}
}
如果你对observables一窍不通,赶紧的,看这里
错误 #5:错误使用*ngFor
在angular2里,我们介绍了一个新概念叫"structural directives",用来描述那些根据表达式在DOM上或增加、或删除元素的指令。和其他指令不同,"structural directive"要么作用在template tag上、 要么配合template attribute使用、要么前缀"*"作为简写语法糖。因为这个新语法特性,初学者常常犯错。
你能分辨出来以下错误么?
错误的ngFor用法
// a:
<div *ngFor="#item in items">
<p> {{ item }} </p>
</div>
// b:
<template *ngFor #item [ngForOf]="items">
<p> {{ item }} </p>
</template>
// c:
<div *ngFor="#item of items; trackBy=myTrackBy; #i=index">
<p>{{i}}: {{item}} </p>
</div>
来,一步步解决错误
5a:把"in"换成"of"
// incorrect
<div *ngFor="#item in items">
<p> {{ item }} </p>
</div>
如果有AngularJS 1经验,通常很容易犯这个错。在AngularJS 1里,相同的repeater写作ng-repeat="item in items"。
angular2将"in"换成"of"是为了和ES6中的for-of循环保持一致。也需要记住的是,如果不用"*"语法糖,那么完整的repeater写法要写作ngForOf, 而非ngForIn
// correct
<div *ngFor="#item of items">
<p> {{ item }} </p>
</div>
5b:语法糖和完整语法混着写
// incorrect
<template *ngFor #item [ngForOf]="items">
<p> {{ item }} </p>
</template>
混着写是没必要的 - 而且事实上,这么写也不工作。当你用了语法糖(前缀"*")以后,angular2就会把她当成一个template attribute,而不是一般的指令。具体来说,解析器拿到了ngFor后面的字符串, 在字符串前面加上ngFor,然后当作template attribute来解析。如下代码:
<div *ngFor="#item of items">
会被当成这样:
<div template="ngFor #item of items">
当你混着写时,他其实变成了这样:
<template template="ngFor" #item [ngForOf]="items">
从template attribute角度分析,发现template attribute后面就只有一个ngFor,别的什么都没了。那必然解析不会正确,也不会正常运行了。
如果从从template tag角度分析,他又缺了一个ngFor指令,所以也会报错。没了ngFor指令,ngForOf都不知道该对谁负责了。
可以这样修正,要么去掉"*"写完整格式,要么就完全按照"*"语法糖简写方式书写
// correct
<template ngFor #item [ngForOf]="items">
<p> {{ item }} </p>
</template>
// correct
<p *ngFor="#item of items">
{{ item }}
</p>
5c:在简写形式里用了错误的操作符
// incorrect
<div *ngFor="#item of items; trackBy=myTrackBy; #i=index">
<p>{{i}}: {{item}} </p>
</div>
为了解释这儿到底出了什么错,我们先不用简写形式把代码写对了看看什么样子:
// correct
<template ngFor #item [ngForOf]="items" [ngForTrackBy]="myTrackBy" #i="index">
<p> {{i}}: {{item}} </p>
</template>
在完整形式下,结构还是很好理解的,我们来试着分解一下:
我们通过输入属性向
ngFor里传入了两组数据:绑定在
ngForOf上的原始数据集合items绑定在
ngForTrackBy上的自定义track-by函数
用
#声明了两个local template variables,分别是:#i和#item。ngFor指令在遍历items时,给这两个变量赋值i是从0开始的items每个元素的下标item是对应每个下标的元素
当我们通过"*"语法糖简写代码时,必须遵守如下原则,以便解析器能够理解简写语法:
所有配置都要写在
*ngFor的属性值里通过
=操作符设置local variable通过
:操作符设置input properties去掉input properties里的
ngFor前缀,譬如:ngForOf,就只写成of就可以了用分号做分隔
按照上述规范,代码修改如下:
// correct
<p *ngFor="#item; of:items; trackBy:myTrackBy; #i=index">
{{i}}: {{item}}
</p>
分号和冒号其实是可选的,解析器会忽略它们。写上仅仅是为了提高代码可读性。因此,也可以再省略一点点:
// correct
<p *ngFor="#item of items; trackBy:myTrackBy; #i=index">
{{i}}: {{item}}
</p>
结论
希望本章的解释对你有用。Happy coding!
原文地址:5 Rookie Mistakes to Avoid with Angular 2
来源:https://segmentfault.com/a/1190000004969541
Angular2新人常犯的5个错误的更多相关文章
- Web开发人员常犯的10个错误
说到开发一个运行在现代网络中的网站:Web开发人员需要选择虚拟主机平台和底层数据存储,准备编写HTML.CSS和JavaScript用的工具,要有设计执行方式,以及一些可用的JavaScript库/框 ...
- Python开发者最常犯的10个错误
Python是一门简单易学的编程语言,语法简洁而清晰,并且拥有丰富和强大的类库.与其它大多数程序设计语言使用大括号不一样 ,它使用缩进来定义语句块. 在平时的工作中,Python开发者很容易犯一些小错 ...
- Java程序员常犯的10个错误
本文总结了Java程序员常犯的10个错误. #1. 把Array转化成ArrayList 把Array转化成ArrayList,程序员经常用以下方法: List<String> lis ...
- OI中常犯的傻逼错误总结
OI中常犯的傻逼错误总结 问题 解决方案 文件名出错,包括文件夹,程序文件名,输入输出文件名 复制pdf的名字 没有去掉调试信息 调试时在后面加个显眼的标记 数组开小,超过定义大小,maxn/ ...
- Spring常犯的十大错误,你踩过吗?
1.错误一:太过关注底层 我们正在解决这个常见错误,是因为 "非我所创" 综合症在软件开发领域很是常见.症状包括经常重写一些常见的代码,很多开发人员都有这种症状. 虽然理解特定库的 ...
- Java开发人员最常犯的10个错误
这个列表总结了10个Java开发人员最常犯的错误. Array转ArrayList 当需要把Array转成ArrayList的时候,开发人员经常这样做: List<String> list ...
- C# 程序员最常犯的 10 个错误(转)
关于C#关于本文常见错误 #1:把引用当做值来用,或者反过来常见错误 #2:误会未初始化变量的默认值常见错误 #3:使用不恰当或未指定的方法比较字符串常见错误 #4:使用迭代式 (而不是声明式)的语句 ...
- C# 程序员最常犯的 10 个错误http://www.oschina.net/translate/top-10-mistakes-that-c-sharp-programmers-make
来源:http://www.oschina.net/translate/top-10-mistakes-that-c-sharp-programmers-make 关于C# C#是达成微软公共语言运行 ...
- python开发者常犯的10个错误(转)
常见错误1:错误地将表达式作为函数的默认参数 在Python中,我们可以为函数的某个参数设置默认值,使该参数成为可选参数.虽然这是一个很好的语言特性,但是当默认值是可变类型时,也会导致一些令人困惑的情 ...
随机推荐
- 当activity改变时,我们如何处理它
用户和系统触发的事件,可能造成一个activity状体的改变.这个文档描述了一些常见的情况,和如何去处理这些改变. 原网站:https://developer.android.google.cn/g ...
- Redis学习01_redis安装部署(centos)
原文: http://www.cnblogs.com/herblog/p/9305668.html Redis学习(一):CentOS下redis安装和部署 1.基础知识 redis是用C语言开发的 ...
- React Native 'config.h' file not found 问题、 'glog/logging.h' file not found 问题、configure: error: C compiler cannot create executables问题解决过程记录
1.在github 上面 git clone 一个RN 项目代码,npm install (yarn)后,准备运行iOS工程,发现'config.h' file not found ,恶心!!! 百度 ...
- 1090. Highest Price in Supply Chain (25)-dfs求层数
给出一棵树,在树根出货物的价格为p,然后每往下一层,价格增加r%,求所有叶子节点中的最高价格,以及该层叶子结点个数. #include <iostream> #include <cs ...
- 《Linux内核分析》课程第三周学习总结
姓名:何伟钦 学号:20135223 ( *原创作品转载请注明出处*) ( 学习课程:<Linux内核分析>MOOC课程http://mooc.study.163.com/course/U ...
- 20135337朱荟潼 Linux第三周学习总结 ——Linux内核源代码简介
朱荟潼 + 原创作品转载请注明出处 + <Linux内核分析>MOOC课http://mooc.study.163.com/course/USTC 1000029000 知识笔记 1.ar ...
- 编码用命令执行的C语言词语统计程序
需求介绍 程序处理用户需求的模式为: wc.exe [parameter][filename] 在[parameter]中,用户通过输入参数与程序交互,需实现的功能如下: 1.基本功能 支持 -c ...
- Validform验证时可以为空,否则按照指定格式验证
在使用Validform v5.3.2时(http://validform.rjboy.cn/) 问题:可以为空,但不为空时需要按照指定格式验证数据 查看文档: 5.2.1版本之后,datatype支 ...
- ElasticSearch 2 (37) - 信息聚合系列之内存与延时
ElasticSearch 2 (37) - 信息聚合系列之内存与延时 摘要 控制内存使用与延时 版本 elasticsearch版本: elasticsearch-2.x 内容 Fielddata ...
- Alpha 冲刺一
团队成员 051601135 岳冠宇 051604103 陈思孝 031602629 刘意晗 031602248 郑智文 031602234 王淇 会议照片 项目燃尽图 项目进展 界面(简陋) 登录界 ...