# 初识泛型 如果你还不能够顺利的、思绪清楚的完成上节的答案,仅仅说明我们重复的还不够。编程其实就是个熟练工,简单的事情重复做你就是专家;如果仅仅重复还不够,那就用心做,因为重复的事情认真做你就是赢家! 我们将具体的答案放到了本节最后,个人按自己的实际情况进行查看。 ## 属性初始化 我们曾经在添加组件中如下初始化`teacher`: ```typescript teacher = { name: '', username: '', email: '', sex: true }; ``` 这当然是可以的。但也有一些的小的弊端,比如当网络情况不是特别理想的时候,则会出现一些不如愿的情况。比如我们使用如下代码来模似请求数据时发生了1.5秒的延迟: ```typescript +++ b/first-app/src/app/edit/edit.component.ts @@ -1,6 +1,7 @@ import {Component, OnInit} from '@angular/core'; import {ActivatedRoute} from '@angular/router'; import {HttpClient} from '@angular/common/http'; +import {delay} from 'rxjs/operators'; @Component({ selector: 'app-edit', @@ -27,6 +28,7 @@ export class EditComponent implements OnInit { const url = 'http://angular.api.codedemo.club:81/teacher/' + id; // 发起请求,成功时并打印请求结果,失败时打印失败结果 this.httpClient.get<any>(url) + .pipe(delay(1500)) .subscribe(data => this.teacher = data, error => console.log('失败', error) ); ``` - 👋 此段为示例扩展代码,无需记忆。 则会发生在1.5秒前,性别会默认选中男;而1.5秒后,性别又会变成女。 ![image-20210227105729188](https://img.kancloud.cn/c2/0a/c20a9f96e601caa8bd825ceafde60236_1000x490.png) 为了规避这种情况,在初始化时,我们更愿意将其初始化一个空对象`{}`: ```typescript teacher = {} ``` 然后使用`as`来将其标明(强制转换)为我写的类型: ```typescript export class EditComponent implements OnInit { // 使用as进行类型转换 - teacher = ➊{ - name: '', - username: '', - email: '', - sex: true + teacher = ➊{} as ➋{ + name: string, + username: string, + email: string, + sex: boolean }; ``` - ➊ `=`后面跟的是值。 - ➋ `as`后面跟的是类型。 使用上述代码我们将`teacher`赋值为`{}`并将其类型声明为`{name: string, username: string, email: string, sex: boolean}`。 ## 初识泛型 泛型基本上是所有强类型面向对象语言支持的特性,它的作用简单来描述就是:统一某个对象(方法)前后的数据格式。 `httpClient.get`方法在默认返回的类型为 `Object`,我们为`teacher`规定了类型后,编辑器则会发生一个编译错误,表示:无法将一个类型为`Object`的变量赋值给特定`{name: string, username: string, email: string, sex: boolean}`类型的变量。这是由于`Object`类型表示一个空对象,该对象上没有任何属性,当然也必然不会有`name、username、email、sex`几个我们也经声明需要的属性了。 ![image-20210227115404670](https://img.kancloud.cn/54/46/5446806421c4185f91e04e506fcba84a_1141x272.png) TypeScript近年的份额稳步提升,三个前端框架Angular, Reactive, Vue均投入到了TypeScript的怀抱,与TypeScript强类型能够在编译阶段检查出错误的特性是分不开的。 `httpClient`除提供了返回`Object`的`get`方法外,还提供了可以指定返回任意类型的`get<T>`方法,这其中的`<T>`是一个**泛型**,**泛**可以理解为广泛、宽泛,白话文来说便是:你说我是什么类型,我就可以是什么类型;**泛**还可以理解为**规范**,白话文来说便是:你规定了是什么类型,就必须是什么类型。以`httpClient.get<T>`为例: ### 宽泛性 我们可以将这里的`T`换成任意类型,比如: ```typescript httpClient.get<boolean>; httpClient.get<number>; httpClient.get<string>; httpClient.get<{}>; httpClient.get<object>; httpClient.get<{id: number}>; httpClient.get<any>; ``` 表示:我们此时发起的后台请求,将分别返回`boolean, number...`类型。 而宽泛并不代表任意,在声明泛型对应的类型时,我们必须严格的依照后台接口的返回情况。确认的说:后台返回的类型是什么,我们就应该在此泛型中声明什么样的数据类型: ```typescript @@ -26,7 +26,7 @@ export class EditComponent implements OnInit { // 拼接请求URL const url = 'http://angular.api.codedemo.club:81/teacher/' + id; // 发起请求,成功时并打印请求结果,失败时打印失败结果 - this.httpClient.get<any>(url) + this.httpClient.get<{name: string, username: string, email: string, sex: boolean}>(url) .subscribe(data => this.teacher = data, error => console.log('失败', error) ); ``` 当然了,我们也可以将后台返回的类型规定为`any`,表示后台返回了一个非常非常宽泛类型的数据,该数据有能力给任何类型赋值。比如以下代码都是合规的: ```typescript const a = null as any; const b: number = a; // 将a赋值为number类型的b const c: string = a; const d: boolean = a; const e: object = a; const f: { id: number } = a; const g: any = a; ``` ### 规范性 宽泛的目的是为了规范,比如我们刚刚在`get<T>`中将`T` 声明为`{name: string, username: string, email: string, sex: boolean}`,则表示请求成功后的返回值类型也为`{name: string, username: string, email: string, sex: boolean}` ```typescript this.httpClient.get<{ name: string, username: string, email: string, sex: boolean }>(url) .subscribe(👉data => this.teacher = data, error => console.log('失败', error) ); ``` - 👉 data的类型与规定的泛型类型相一致 此时,如若我们想获取规定泛型上没有属性,比如尝试获取返回值类型上的`id`属性,则会发生编译错误: ![image-20210227124717221](https://img.kancloud.cn/05/0d/050dc9a834dbefc90b446c206b41d650_835x145.png) 所以对于泛型而言:宽泛是方法、规范是目的。 ### 泛型化 最终我们结合泛型,调用`httpClient.get<T>`方法,最终代码如下: ```typescript constructor(private activeRoute: ActivatedRoute, @@ -26,8 +26,8 @@ export class EditComponent implements OnInit { // 拼接请求URL const url = 'http://angular.api.codedemo.club:81/teacher/' + id; // 发起请求,成功时并打印请求结果,失败时打印失败结果 - this.httpClient.get<any>(url) - .subscribe(data => this.teacher = data, + this.httpClient.get<{ name: string, username: string, email: string, sex: boolean }>(url) + .subscribe(data => this.teacher = data, error => console.log('失败', error) ); } ``` ## 本节作业 作业一:程序开发时有个重要原则:(非必要)不造重复的轮子,而我们在当前代码中出现了重复的`{ name: string, username: string, email: string, sex: boolean }` 分别位于: ```typescript teacher = {} as { name: string, username: string, email: string, sex: boolean }; this.httpClient.get<{ name: string, username: string, email: string, sex: boolean }>(url) ``` - 写法不同而已,前面的写成了多行,后台的写成了一行。 作业二:打开`scr/app/app.component.ts`,找到其`teachers`声明的代码,将其类型由`any[]`改成更规范的格式。 ```typescript // 初始化教师数组 teachers = [] as any[]; ``` 请搜索相关的知识,将两个重复的轮子变成一个。 | 名称 | 地址 | 备注 | | ------------------ | ------------------------------------------------------------ | ---- | | TypeScript基础类型 | [https://www.tslang.cn/docs/handbook/basic-types.html](https://www.tslang.cn/docs/handbook/basic-types.html) | | | TypeScript泛型 | [https://www.tslang.cn/docs/handbook/generics.html](https://www.tslang.cn/docs/handbook/generics.html) | | | 初识泛型 | [https://www.kancloud.cn/yunzhiclub/springboot_angular_guide/1369061](https://www.kancloud.cn/yunzhiclub/springboot_angular_guide/1369061) | | | 本节源码 | [https://github.com/mengyunzhi/angular11-guild/archive/step2.4.4.zip](https://github.com/mengyunzhi/angular11-guild/archive/step2.4.4.zip) | | ------ ## 上节答案 ```typescript +++ b/first-app/src/app/edit/edit.component.ts @@ -8,7 +8,14 @@ import {HttpClient} from '@angular/common/http'; styleUrls: ['./edit.component.css'] }) export class EditComponent implements OnInit { - teacher: any; + // 使用as进行类型转换 + teacher = {} as { + name: string, + username: string, + email: string, + sex: boolean + }; + constructor(private activeRoute: ActivatedRoute, private httpClient: HttpClient) { } @@ -19,10 +26,13 @@ export class EditComponent implements OnInit { // 拼接请求URL const url = 'http://angular.api.codedemo.club:81/teacher/' + id; // 发起请求,成功时并打印请求结果,失败时打印失败结果 - this.httpClient.get(url) + this.httpClient.get<any>(url) .subscribe(data => this.teacher = data, error => console.log('失败', error) ); } + onSubmit(): void { + console.log('点击提交按钮'); + } } ``` ```html +++ b/first-app/src/app/edit/edit.component.html @@ -1,16 +1,19 @@ {{teacher | json}} -<div> - 姓名:<input value="张三"> -</div> -<div> - 用户名:<input value="zhangsan"> -</div> -<div> - Email: <input value="zhangsan@yunzhi.club"> -</div> -<div> - 性别:<input type="radio" checked="true">男 <input type="radio">女 -</div> -<div> - <button>保存</button> -</div> +<form (ngSubmit)="onSubmit()"> + <div> + 姓名:<input value="张三" name="name" [(ngModel)]="teacher.name"> + </div> + <div> + 用户名:<input value="zhangsan" name="username" [(ngModel)]="teacher.username"> + </div> + <div> + Email: <input value="zhangsan@yunzhi.club" name="email" [(ngModel)]="teacher.email"> + </div> + <div> + 性别:<input type="radio" name="sex" [value]="true" [(ngModel)]="teacher.sex">男 + <input type="radio" name="sex" [value]="false" [(ngModel)]="teacher.sex">女 + </div> + <div> + <button>保存</button> + </div> +</form> ```