# TypeScript 高级类型
- class 类型
- 类型兼容性
- 交叉类型
- 泛型 和 keyof
- 索引签名类型 和 索引查询类型
- 映射类型
# 1. class 类型
说明:
- TS 支持 class 关键字,并为其添加了类型注解和其他语法
示例:
class Person {
age: number;
gender = '男'
}
let p: Person;
p = new Person();
p.age;
p.gender;
# 1.1. 构造函数
说明:
- 用于 成员初始化
- 不需要返回值类型
示例:
class Person {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}
const p = new Person('张三', 18);
console.log(p.name, p.age);
# 1.2. 实例方法
说明:
- 方法的类型注解与函数的相同
示例:
class Point {
x = 10;
y = 20;
scale(n: number): void {
this.x *= n;
this.y *= n;
}
}
const p = new Point();
p.scale(10);
console.log(p.x, p.y);
# 1.3. extends(继承)
说明:
- JS 中也有 extends
- 子类复用父类的属性和方法
示例:
class Animal {
move() {
console.log('move');
}
}
class Dog extends Animal {
name = '二哈';
bark() {
console.log('汪汪');
}
}
const dog = new Dog();
dog.move();
dog.bark();
console.log(dog.name);
# 1.4. implements(实现接口)
说明:
- 类 必须实现 接口 中所有的属性和方法
示例:
interface Singable {
sing(): void;
}
class Person implements Singable {
sing() {
console.log('你是我的小苹果!');
}
}
# 1.5. 可见性修饰符
说明:
- 用于控制在 类外 是否可以访问类的属性和方法
- 可见性修饰符:
public
: 公开的,默认的。当前类及实例、子类及实例 都可以访问。protected
: 受保护的。当前类、子类中可以访问到。(非实例对象)private
: 私有的。当前类才可以访问
示例:
class Animal {
private __name__: string;
private __run__() {
console.log('run');
}
protected move(): void {
console.log('move');
}
public setName(name: string) {
this.__name__ = name;
}
public getName(): string {
return this.__name__;
}
}
class Dog extends Animal {
run() {
this.move();
}
}
const animal = new Animal();
const dog = new Dog();
// animal.move(); // 访问不了
console.log( animal.getName() );
# 1.6. readonly(只读修饰符)
说明:
- readonly,表示只读,修饰属性
- 类的成员属性 上使用,表示只能在构造函数中赋值
- 接口或对象类型注解 中使用,表示属性是只读的
示例:
class Person {
readonly name: string = '';
// 如果不明确设置类型注解,则推论为字面量类型
readonly age = 18;
constructor(name: string, age: number) {
this.name = name;
// this.age = age;
}
}
interface IPerson {
readonly name: string;
}
let p1: IPerson = { name: '张三' };
// 不能修改
// p1.name = ''
let p2: { readonly name: string } = { name: '李四' };
// p2.name = ''
# 2. 类型兼容性
# 2.1. 介绍
说明:
- 有两种类型系统:结构化类型系统,标明类型系统
- 结构化类型系统,也叫鸭子类型,只关注值所具有的形状
- 如果两个对象具有相同的形状,则认为它们是同一类型
- TS 采用结构化类型系统
示例:
class Male { name: string }
class Female { name: string }
// 发生的类型兼容
let p1: Male = new Female();
# 2.2. 类之间的类型兼容
说明:
- 成员多的可以赋值给成员少的
示例:
class Point2D { x: number; y: number }
class Point3D { x: number; y: number; z: number }
let p: Point2D = new Point3D();
# 2.3. 接口之间的类型兼容
说明:
- 接口之间的兼容性,类似与 class,多的可以赋值给少的
示例:
interface IPoint { x: number, y: number }
interface IPoint2D { x: number, y: number }
interface IPoint3D { x: number, y: number, z: number }
let p1: IPoint;
let p2: IPoint2D;
let p3: IPoint3D;
// 相同属性
p1 = p2;
// 多的赋值给少的
p1 = p3;
# 2.4. 接口与类之间的类型兼容
说明:
- 接口 与 类 之间的兼容性,类似于 class
示例:
interface IPoint2D { x: number, y: number }
class Point3D { x: number; y: number; z: number }
let p2: IPoint2D;
p2 = new Point3D();
# 2.5. 函数之间的类型兼容
函数之间兼容性比较复杂,需要考虑:
- 参数个数
- 参数类型
- 返回值类型
# 2.5.1. 参数个数
说明:
- JS 中省略用不到的参数非常常见
- TS 中可以将 参数少的 赋值给 参数多的
示例:
type F1 = (a: number) => void;
type F2 = (a: number, b: number) => void;
let f1: F1 = (a) => a;
let f2: F2;
f2 = f1;
/*
forEach 需要的回调函数的参数类型为:
(value: number, index: number, array: number[]) => void
*/
let arr = [1, 2, 3];
arr.forEach(() => {});
arr.forEach((item) => {});
arr.forEach((item, index) => {});
arr.forEach((item, index, array) => {});
# 2.5.2. 参数类型
说明:
- 相同位置的参数的类型,分两种情况:基本类型、对象类型(类、接口、对象)
- 基本类型:要相同
- 对象类型:摊开后,参数少的 可赋值给 参数多的
示例1:
// 基本类型
type F1 = (a: number) => void;
type F2 = (b: number) => void;
let f1: F1 = (x) => x;
let f2: F2;
f2 = f1;
示例2:
// 对象类型
type F1 = (p1: { x: number }) => void;
type F2 = (p2: { x: number, y: number }) => void;
let f1: F1 = (p) => {};
let f2: F2;
f2 = f1;
/*
Type 'F2' is not assignable to type 'F1'.
Types of parameters 'p2' and 'p1' are incompatible.
*/
f1 = f2;
# 2.5.3. 返回值类型
说明:
- 返回值类型为 基本类型,两类型要相同
- 返回值类型为 对象类型,多的可以赋值给少的 (对象兼容性)
示例:
type F1 = () => string;
type F2 = () => string;
let f1: F1;
let f2: F2;
// f1 = f2;
f2 = f1;
type F3 = () => { a: number };
type F4 = () => { a: number, b: string };
let f3: F3;
let f4: F4;
f3 = f4;
// f4 = f3; // 不行
# 3. 交叉类型
说明:
- 功能类似于接口继承,用于组合多个类型为一个类型
- 常用于对象类型
- 使用
&
连接多个类型
示例:
interface Person { name: string }
interface Contact { phone: string }
type PersonDetail = Person & Contact;
let p: PersonDetail = {
name: '张三',
phone: '123'
}
# 3.1. 交叉类型 vs 接口继承
- 相同点:都可以实现对象类型的组合
- 不同点:处理同名属性的方式不一样
interface A { x: string }
/*
TS2430: Interface 'B' incorrectly extends interface 'A'.
Types of property 'x' are incompatible.
Type 'number' is not assignable to type 'string'.
*/
// interface B extends A { x: number }
interface C { x: string }
interface D { x: number }
type E = C & D;
// (property) x: never
let e: E = { x: 1 };
# 4. 泛型
# 4.1. 泛型函数
# 4.1.1. 基本使用
说明:
- 泛型,在保证类型安全前提下,让函数支持多种类型。
- 常用于 函数、接口、class
泛型函数:
/*
定义泛型函数:
函数名称后面的 `<Type>`:
Type 为类型变量的名称,可以在类型注解中使用
*/
function id<Type>(value: Type): Type {
return value;
}
/*
调用泛型函数:
调用函数时,给类型变量赋值
*/
let num: number = id<number>(123);
let str: string = id<string>('a');
# 4.1.2. 简化调用泛型函数
说明:
- 调用泛型函数时,可以省略
<Type>
,交给 TS 的类型参数推断机制来做 - 如果类型推断不准确,再显式给类型变量赋值
示例:
function id<Type>(value: Type): Type {
return value;
}
// 等价于 let num = id<number>(123);
let num = id(123);
# 4.2. 泛型约束
说明:
- 类型变量可以是任意的类型,也就是说它无法使用类型独有的属性和方法
- 限制泛型的类型变量的值的范围,有两种方式:
- 指定更加具体的类型
- 添加约束
# 4.2.1. 指定更加具体的类型
比如,将类型修改为 Type[]
:
function id<Type>(value: Type[]): Type[] {
console.log(value.length);
return value;
}
# 4.2.2. 添加约束
说明:
- 通过关键字
extends
给泛型的类型变量添加约束
示例:
interface ILength { length: number }
// 类型变量 Type 要满足 ILength 的要求,即要求有 length 属性
function id<Type extends ILength>(value: Type): Type {
console.log(value.length);
return value;
}
id([1, 2, 3]);
id({ length: 2, '0': 1, '1': 2 });
# 4.3. 多个类型变量
说明:
- 泛型的类型变量可以有多个,并且类型变量之间可以约束
示例:
/*
keyof 对象
生成对象键名称的联合类型
如 typeof { name: '1', age: 2 } => 'name' | 'age'
*/
function getProp<Type, Key extends (keyof Type)>(obj: Type, key: Key) {
return obj[key];
}
let stuName: string = getProp({ name: '张三', age: 18 }, 'name');
let age: number = getProp({ name: '张三', age: 18 }, 'age');
# 4.4. 泛型接口
# 4.4.1. 基本使用
说明:
- 在接口名称后面添加
<类型变量>
,这个接口就变成泛型接口了 - 接口中所有成员都可以使用该类型变量
- 使用泛型接口时,要显式指定类型变量的值
示例:
interface IStudent<Type> {
id: Type;
getId(): Type;
setId(id: Type): Type;
}
let stu: IStudent<string> = {
id: '123',
getId(): string {
return this.id;
},
setId(id: string): string {
this.id = id;
return this.id;
}
}
# 4.4.2. 数组泛型接口
说明:
- 数组在 TS 中就是一个泛型接口
- 在官方 lib.es5.d.ts 文件中有数组的接口类型
示例:
// lib.es5.d.ts
interface Array<T> {
/**
* Performs the specified action for each element in an array.
* @param callbackfn A function that accepts up to three arguments. forEach calls the callbackfn function one time for each element in the array.
* @param thisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.
*/
forEach(callbackfn: (value: T, index: number, array: T[]) => void, thisArg?: any): void;
// ...
}
# 4.5. 泛型类
说明:
- 定义时,在类名称后面声明
<类型变量>
- 调用时,在类名称后面给
<类型变量>
赋值
示例:
class GenericNumber<T> {
defaultValue: T;
constructor(value: T) {
this.defaultValue = value;
}
add(num: T): T {
return this.defaultValue;
}
}
// 显式设置类型变量的值
let n1 = new GenericNumber<number>(10);
// 类型推论
let n2 = new GenericNumber(10);
# 4.6. 泛型工具类型
# 4.6.1. 介绍
说明:
- TS 内置了一些常用的工具类型
- 它们都是基于泛型实现的,可以直接在代码中使用
工具类型:
Partial<Type>
Readonly<Type>
Pick<Type, Keys>
Record<Keys, Type>
# 4.6.2. Partial
说明:
Partial<Type>
- 创建一个结构与
Type
完全相同的类型,但所有属性都是可选的
示例:
interface IPerson { name: string; age: number; }
/*
{ name?: string, age?: number }
*/
type PartialPerson = Partial<IPerson>;
# 4.6.3. Readonly
说明:
Readonly<Type>
- 创建一个结构与
Type
完全相同的类型,但所有属性都是只读的
示例:
interface IPerson { name: string; age: number; }
/*
{ readonly name: string, readonly age: number }
*/
type ReadonlyPerson = Readonly<IPerson>;
# 4.6.4. Pick
说明:
Pick<Type, Keys>
- 从
Type
中选择Keys
指定的属性来创建新类型
示例:
interface IPerson { name: string; age: number; gender: string }
/*
{ name: string, age: number }
*/
type PickPerson = Pick<IPerson, 'name' | 'age'>;
# 4.6.5. Record
说明:
Record<Keys, Type>
- 创建一个对象类型,
Keys
指定有哪些属性,Type
指定属性的类型 - 所有属性的类型都为
Type
示例:
/*
{ name: string, gender: string }
*/
type RecordObj = Record<'name' | 'gender', string>;
# 5. 索引签名类型
# 5.1. 基本使用
说明:
- 绝大多数情况下,在使用对象前就确定对象的结构,并未对象添加准确的类型
- 当无法确定对象有哪些属性(或对象中可以出现任意的属性)时,可以使用索引签名类型
示例:
interface AnyObject {
/*
key 为占位符,可以为任意的标识符
对象中的属性名称都为字符串
*/
[key: string]: number
}
let obj: AnyObject = {
a: 1,
b: 2,
}
# 5.2. 数组的键
说明:
- 数组是特殊的对象,键(索引)是数值类型
- 数组对应的泛型接口中,也用到了索引签名类型
示例:
// lib.es5.d.ts
interface Array<T> {
// ...
// 数组的 index,及对应的值类型
[n: number]: T;
}
// 模拟数组
interface MyArray<T> {
[index: number]: T;
}
let arr: MyArray<number> = [1, 2]
console.log( arr[0] );
# 6. 映射类型
# 6.1. 基本使用
说明:
- 基于已有类型创建新类型(对象类型)
- 映射类型是基于索引签名类型的,语法类似
- 映射类型只能在类型别名中使用,不能在接口中使用
示例:
type PropsKeys = 'x' | 'y' | 'z';
/*
{ x: number, y: number, z: number }
*/
type Type = {
[Key in PropsKeys]: number;
}
# 6.2. keyof
说明:
keyof 对象类型
: 获取对象类型中所有键的联合类型keyof { x: number, y: number }
:"x" | "y"
示例:
type Props = { x: number, y: number, z: number }
/*
配合映射类型使用:
{ x: number, y: number, z: number }
*/
type Type = {
[Key in (keyof Props)]: number;
};
# 6.3. Partial 的实现
lib.es5.d.ts:
/**
* Make all properties in T optional
*/
type Partial<T> = {
/*
keyof T
表示获取 T 中所有键的联合类型
?
表示可选的
T[P]
表示获取 T 中对应键 P 的类型
*/
[P in keyof T]?: T[P];
};
# 6.4. 索引查询类型
说明:
T[P]
语法,叫做 索引查询类型(或索引访问类型)- 作用:用来查询属性的类型
示例:
type Props = {
x: number,
y: string,
z: boolean,
i: number,
}
// 获取单个属性的类型
type Type1 = Props['x']; // number
// 获取多个属性类型
type Type2 = Props['x' | 'y']; // number | string
// 获取所有属性的类型
type Type3 = Props[keyof Props]; // number | string | boolean
上一篇: 下一篇:
本章目录