前集回顾
在上一章里我们讲了如何为angular2 搭建开发环境(还没搭起来的赶紧去看哦),并使之跑起来我们的第一个"My First Angular 2 App"。当然也有不少朋友反映环境搭建似乎比较复杂,整整一篇教程,最后只简单输出了一句话!这里我要说一句,学习新知识的确有一个阵痛的过程,尤其像angular2 这种框架,引入了大量以前"前端"并不关心(没有需求)的技术栈,这使得对于之前没有接触过这些概念的朋友的学习曲线陡然飙升,相信不少人看了上一章里开篇时的那些名词后已经认识到这一点了!本教程主打实际操作,但也不会完全忽略理论,我们边做边理解。今天就接着上一章的余温,我们来写一个简单component 。
本章源码:component
本章使用angular2 版本为:2.4.5 ,webpack 版本为: 2.2.0
先来看看我们将要完成的效果图:
(注意动画的部分)非常简单的一个component ,有木有?那好,我们现在要做的就是为这样一个component 描述需求:
她要能接受一个object 用来描述初始值,如:isChecked (是否选中)、 txt (显示文本)
当选中时,需要有横线覆盖文本;反之亦然
当用户点击复选框时,需要向上广播该事件,由父组件(调用方)决定点击时该做什么。这里我们需要在父组件里改变component 的isChecked 状态,并使component 重绘
她必须是一个处理Unidirectional Data Flow(单向数据流)的component ,意思是传入参数必须不可变(Immutable)
注:第4步里,我们使用Unidirectional Data Flow 模型来更新数据,并没有涉及到任何Reactive Programming的知识点
为了完成以上需求,我们需要了解下面知识点
什么是component
或者这么问,AngularJS里有directive;angular2里有component ,他们是什么关系?该如何理解angular2里的component ?原谅我这里就不再详述AngularJS里的directive了,直接介绍component :
Component : 简单说,就是带template 的directive ,也是最常见的组件形式。譬如:上一章中,ts/app.ts 里的AppComponent 。
Structural directive : 通过增加/删除DOM 元素改变DOM 布局的directive 。譬如:NgFor和NgIf
Attribute directive : 控制DOM 元素显示/隐藏,或者改变元素行为的directive 。譬如:NgStyle
设计use case
看过我之前介绍以BDD手写依赖注入(dependency injection)的朋友应该已经对"行为驱动"多少有些了解了。当我们需要设计一个API或者组件时,最佳的方式就是先设计她的使用场景,从行为开始,对该API或者组件进行描述,最后再将缺失的“实现”部分补全就可以了。
假设我们将在上一章中的AppComponent 里使用这个新的component ,根据之前的需求描述,我们的使用场景应该是这个样子的 :
import {Component,OnInit} from '@angular/core';
import {Item} from './CheckableItem';
//该component使用checkable-item作为selector
//并可以通过[item]属性传入一个object
//还可以通过(onItemClicked)接受一个点击事件
@Component({
selector: 'my-app',template: `
<h1>My First Angular 2 App</h1>
<checkable-item [item]="itemInfo" (onItemClicked)="toggle($event)">
</checkable-item>
`
})
export class AppComponent implements OnInit {
itemInfo: Item;
//当实现OnInit接口时,必须重写ngOnInit方法
//关于OnInit,详见:
//https://angular.io/docs/ts/latest/guide/lifecycle-hooks.html#!#hooks-overview
ngOnInit() {
//设定初始值
//根据需求第1条,包含两个属性
this.itemInfo = {
isChecked: false,txt: 'Hello World!'
};
}
//根据需求第3条,点击component后,事件要
//冒泡到父组件(调用方)
toggle(item: Item) {
//当获取到CheckableItem的点击事件时,
//给itemInfo重新赋值,并将isChecked置反
//注:重新赋值是根据需求第4条的不可变性
this.itemInfo = {
isChecked: !item.isChecked,txt: item.txt
};
}
}
实现component
根据上述介绍,再结合之前的效果图,我们要做的当然就是一个标准的Component 。她有template ,并且包含了至少一个input 和一个label 标签。
有了使用场景(行为),接下来就是实现这个CheckableItem 了:
touch ts/CheckableItem.ts
向刚创建的ts/CheckableItem.ts 文件里写入如下内容:
import {Component,Input,Output,EventEmitter,ChangeDetectionStrategy} from '@angular/core';
@Component({
//脏检查策略,OnPush指当且仅当传入参数的reference发生变更时
//触发组件重绘。这和React中的shouldComponentUpdate异曲同工,
//不过更先进(因为React还是需要手动实现的)
//这也是上一步里itemInfo必须重新赋值的原因
changeDetection: ChangeDetectionStrategy.OnPush,selector: 'checkable-item',//仅在当前component作用域下有效的class
styles: [`
.deleted{
text-decoration: line-through;
}
`],//template就如我们需求里的描述那样,由一个input标签和
//一个label标签组成
template: `
<div>
<input type="checkbox" (change)="clickItem($event)">
<label [class.deleted]="item.isChecked">{{ item.txt }}</label>
</div>
`
})
export class CheckableItem {
//item被声明为Input,即会在父组件传入参数时用到
@Input() item: Item;
//onItemClicked被声明为Output,用来在用户点击input标签
//时向上冒泡事件
@Output() onItemClicked = new EventEmitter();
//监听input上的click事件,当用户点击时,首先阻止默认行为
//因为是否变化(重绘)是由父组件决定的
//然后冒泡点击事件
clickItem(e: MouseEvent) {
e.preventDefault();
this.onItemClicked.emit(this.item);
}
}
export interface ToggleItemHandler {
(item: Item): void;
}
export interface Item {
isChecked?: boolean;
txt?: string;
}
有朋友看到这里,对[] 、 () 之类的绑定标签表示不解,这里我们统一来解释:
[target] = "expression" ,将右边表达式对应的值绑定到左边的target 。譬如:在ts/app.ts 里,我们使用[item]="itemInfo" 将itemInfo 对应的值绑定到了组件CheckableItem 的item 上,这样,在CheckableItem 里就可以通过this.item 获取到父组件传进来的参数了。
(target) = "statement" ,将左边的事件传递给了右边的表达式(通常就是事件处理函数)。譬如:在ts/app.ts 里,我们使用(onItemClicked)="toggle($event)" 将CheckableItem 冒泡上来的onItemClicked 事件传递给了toggle 函数。
[class.deleted]="item.isChecked" ,是class 的一种特殊用法,指当item.isChecked 表达式为真时,为该标签的class 里增加deleted ;反之,则删除该标签class 里的deleted
引入声明
打开之前写的index.ts ,增加CheckableItem 引入:
import 'core-js/es6';
import 'core-js/es7/reflect';
import 'zone.js/dist/zone';
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
//引入CheckableItem
import {CheckableItem} from './CheckableItem';
@NgModule({
imports: [ BrowserModule ],declarations: [ AppComponent,CheckableItem ],//引入声明
bootstrap: [ AppComponent ]
})
class AppModule { }
platformBrowserDynamic().bootstrapModule(AppModule);
见证奇迹
OK,事已至此,我们是不是又该启动一把程序看看效果了?
npm start
你又看到了伟大的效果:
下回预告:小刀升级 - 多component 协作 (编辑:李大同)
【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!
|