# 初识泛型
如果你还不能够顺利的、思绪清楚的完成上节的答案,仅仅说明我们重复的还不够。编程其实就是个熟练工,简单的事情重复做你就是专家;如果仅仅重复还不够,那就用心做,因为重复的事情认真做你就是赢家!
我们将具体的答案放到了本节最后,个人按自己的实际情况进行查看。
## 属性初始化
我们曾经在添加组件中如下初始化`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>
```
- 序言
- 第一章 Hello World
- 1.1 环境安装
- 1.2 Hello Angular
- 1.3 Hello World!
- 第二章 教师管理
- 2.1 教师列表
- 2.1.1 初始化原型
- 2.1.2 组件生命周期之初始化
- 2.1.3 ngFor
- 2.1.4 ngIf、ngTemplate
- 2.1.5 引用 Bootstrap
- 2.2 请求后台数据
- 2.2.1 HttpClient
- 2.2.2 请求数据
- 2.2.3 模块与依赖注入
- 2.2.4 异步与回调函数
- 2.2.5 集成测试
- 2.2.6 本章小节
- 2.3 新增教师
- 2.3.1 组件初始化
- 2.3.2 [(ngModel)]
- 2.3.3 对接后台
- 2.3.4 路由
- 2.4 编辑教师
- 2.4.1 组件初始化
- 2.4.2 获取路由参数
- 2.4.3 插值与模板表达式
- 2.4.4 初识泛型
- 2.4.5 更新教师
- 2.4.6 测试中的路由
- 2.5 删除教师
- 2.6 收尾工作
- 2.6.1 RouterLink
- 2.6.2 fontawesome图标库
- 2.6.3 firefox
- 2.7 总结
- 第三章 用户登录
- 3.1 初识单元测试
- 3.2 http概述
- 3.3 Basic access authentication
- 3.4 着陆组件
- 3.5 @Output
- 3.6 TypeScript 类
- 3.7 浏览器缓存
- 3.8 总结
- 第四章 个人中心
- 4.1 原型
- 4.2 管道
- 4.3 对接后台
- 4.4 x-auth-token认证
- 4.5 拦截器
- 4.6 小结
- 第五章 系统菜单
- 5.1 延迟及测试
- 5.2 手动创建组件
- 5.3 隐藏测试信息
- 5.4 规划路由
- 5.5 定义菜单
- 5.6 注销
- 5.7 小结
- 第六章 班级管理
- 6.1 新增班级
- 6.1.1 组件初始化
- 6.1.2 MockApi 新建班级
- 6.1.3 ApiInterceptor
- 6.1.4 数据验证
- 6.1.5 教师选择列表
- 6.1.6 MockApi 教师列表
- 6.1.7 代码重构
- 6.1.8 小结
- 6.2 教师列表组件
- 6.2.1 初始化
- 6.2.2 响应式表单
- 6.2.3 getTestScheduler()
- 6.2.4 应用组件
- 6.2.5 小结
- 6.3 班级列表
- 6.3.1 原型设计
- 6.3.2 初始化分页
- 6.3.3 MockApi
- 6.3.4 静态分页
- 6.3.5 动态分页
- 6.3.6 @Input()
- 6.4 编辑班级
- 6.4.1 测试模块
- 6.4.2 响应式表单验证
- 6.4.3 @Input()
- 6.4.4 FormGroup
- 6.4.5 自定义FormControl
- 6.4.6 代码重构
- 6.4.7 小结
- 6.5 删除班级
- 6.6 集成测试
- 6.6.1 惰性加载
- 6.6.2 API拦截器
- 6.6.3 路由与跳转
- 6.6.4 ngStyle
- 6.7 初识Service
- 6.7.1 catchError
- 6.7.2 单例服务
- 6.7.3 单元测试
- 6.8 小结
- 第七章 学生管理
- 7.1 班级列表组件
- 7.2 新增学生
- 7.2.1 exports
- 7.2.2 自定义验证器
- 7.2.3 异步验证器
- 7.2.4 再识DI
- 7.2.5 属性型指令
- 7.2.6 完成功能
- 7.2.7 小结
- 7.3 单元测试进阶
- 7.4 学生列表
- 7.4.1 JSON对象与对象
- 7.4.2 单元测试
- 7.4.3 分页模块
- 7.4.4 子组件测试
- 7.4.5 重构分页
- 7.5 删除学生
- 7.5.1 第三方dialog
- 7.5.2 批量删除
- 7.5.3 面向对象
- 7.6 集成测试
- 7.7 编辑学生
- 7.7.1 初始化
- 7.7.2 自定义provider
- 7.7.3 更新学生
- 7.7.4 集成测试
- 7.7.5 可订阅的路由参数
- 7.7.6 小结
- 7.8 总结
- 第八章 其它
- 8.1 打包构建
- 8.2 发布部署
- 第九章 总结