TS泛型

在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定具体类型的一种特性

虽然 any 是可以的,但是定义为 any 的时候,其实已经丢失了类型信息

  • 比如传入的是一个 number,那么希望返回的可不是 any 类型,而是 number 类型
  • 所以,需要在函数中可以捕获到参数的类型是 number,并且同时使用它来作为返回值的类型

类型的参数化

  • 通过 <类型> 的方式将类型传递给函数
  • 通过类型推导(type argument inference),自动推到出传入变量的类型
    • 在这里会推导出它们是字面量类型的,因为字面量类型对于函数也是适用的
1
2
3
4
5
6
7
8
9
10
11
12
function bar<Type>(arg: Type): Type {
return arg;
}

// 1.完整的写法
const res1 = bar<number>(123);
const res2 = bar<string>("abc");
const res3 = bar<{ name: string }>({ name: "aaa" });

// 2.省略的写法
const res4 = bar("aaa");
const res5 = bar(111);

react 中useState的应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 元组: useState函数
function useState<Type>(initialState: Type): [Type, (newState: Type) => void] {
let state = initialState;
function setState(newState: Type) {
state = newState;
}

return [state, setState];
}

// 初始化count
const [count, setCount] = useState(100);
const [message, setMessage] = useState("Hello World");
const [banners, setBanners] = useState<any[]>([]);

多个泛型参数

一个函数可以定义多个泛型参数

平时在开发中可能会看到一些常用的名称

  • T:Type 的缩写,类型
  • KVkeyvalue的缩写,键值对
  • EElement的缩写,元素
  • OObject的缩写,对象
1
2
3
4
function foo<K, V>(key: K, value: V): [K, V] {
return [key, value];
}
const result = foo<string, number>("age", 18);

泛型接口

在定义接口时, 为接口中的属性或方法定义泛型类型
在使用接口时, 再指定具体的泛型类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
interface IKun<Type = string> {
name: Type;
age: number;
slogan: Type;
}

const ikun1: IKun<string> = {
name: "kun",
age: 18,
slogan: "哎呦,你干嘛!"
};

const ikun2: IKun<number> = {
name: 123,
age: 20,
slogan: 666
};

// 没有赋值具体的类型,使用Type的默认值(<Type = string>)
// Type没有默认值时,会报错,不会自动推断出类型
const ikun3: IKun = {
name: "ikun",
age: 30,
slogan: "坤坤加油!"
};

泛型类

在定义类时, 为类中的属性或方法定义泛型类型 在创建类的实例时, 再指定特定的泛型类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Point<Type = number> {
x: Type;
y: Type;
constructor(x: Type, y: Type) {
this.x = x;
this.y = y;
}
}

// 没有赋值具体的类型,使用Type的默认值(<Type = number>)
// Type没有默认值时,会自动推断出类型,不会报错
const p1 = new Point(10, 20);
console.log(p1.x);
const p2 = new Point("123", "321");
console.log(p2.x);

泛型约束

希望传入的类型有某些共性,但是这些共性可能不是在同一种类型中

  • 比如stringarray都是有length的,或者某些对象也是会有length属性的
  • 那么只要是拥有length的属性都可以作为的参数类型
  • 这里表示是传入的类型必须有这个属性,也可以有其他属性,但是必须至少有这个成员
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
interface ILength {
length: number;
}

// 1.getLength没有必要用泛型
function getLength(arg: ILength) {
return arg.length;
}

const length1 = getLength("aaaa");
const length2 = getLength(["aaa", "bbb", "ccc"]);
const length3 = getLength({ length: 100 });

// 2.获取传入的内容, 这个内容必须有length属性
// Type相当于是一个变量, 用于记录本次调用的类型, 所以在整个函数的执行周期中, 一直保留着参数的类型
function getInfo<Type extends ILength>(args: Type): Type {
return args;
}

const info1 = getInfo("aaaa");
const info2 = getInfo(["aaa", "bbb", "ccc"]);
const info3 = getInfo({ length: 100 });

在泛型约束中使用类型参数(Using Type Parameters in Generic Constraints)

  • 可以声明一个类型参数,这个类型参数被其他类型参数约束
  • 举个栗子:希望获取一个对象给定属性名的值
    • 需要确保不会获取 obj 上不存在的属性
    • 所以在两个类型之间建立一个约束
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 传入的key类型, obj当中key的其中之一
interface IKun {
name: string;
age: number;
}

type IKunKeys = keyof IKun; // "name"|"age" => 类似于 K = "name" | "age"

function getObjectProperty<O, K extends keyof O>(obj: O, key: K) {
return obj[key];
}

const info = {
name: "aaa",
age: 18,
height: 1.88
};

const name = getObjectProperty(info, "name");

映射类型

有的时候,一个类型需要基于另外一个类型,但是又不想拷贝一份,这个时候可以考虑使用映射类型

  • 大部分内置的工具都是通过映射类型来实现的
  • 大多数类型体操的题目也是通过映射类型完成的

映射类型建立在索引签名语法基础之上

  • 映射类型,就是使用了 PropertyKeys 联合类型的泛型
  • 其中 PropertyKeys 多是通过 keyof 创建,然后循环遍历键名创建一个类型

基本使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// TypeScript提供了映射类型: 函数
// 映射类型不能使用interface定义
// Type = IPerson
// keyof = "name" | "age"
type MapPerson<Type> = {
// 索引类型以此进行使用
[Property in keyof Type]: Type[Property];
};

interface IPerson {
name: string;
age: number;
}

type NewPerson = MapPerson<IPerson>;

修饰符使用

在使用映射类型时,有两个额外的修饰符可能会用到

  • 一个是 readonly,用于设置属性只读
  • 一个是 ? ,用于设置属性可选
1
2
3
4
5
6
7
8
9
10
11
12
type MapPerson<Type> = {
readonly [Property in keyof Type]?: Type[Property];
};

interface IPerson {
name: string;
age: number;
height: number;
address: string;
}

type IPersonOptional = MapPerson<IPerson>;

修饰符符号

可以通过前缀 - 或者 + 删除或者添加这些修饰符

如果没有写前缀,相当于使用了 + 前缀(默认)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
type MapPerson<Type> = {
-readonly [Property in keyof Type]-?: Type[Property];
};

interface IPerson {
name: string;
age?: number;
readonly height: number;
address?: string;
}

type IPersonRequired = MapPerson<IPerson>;

const p: IPersonRequired = {
name: "aaa",
age: 18,
height: 1.88,
address: "北京市"
};