在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定具体类型的一种特性
虽然 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; }
const res1 = bar<number>(123); const res2 = bar<string>("abc"); const res3 = bar<{ name: string }>({ name: "aaa" });
const res4 = bar("aaa"); const res5 = bar(111);
|
react 中useState
的应用
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| function useState<Type>(initialState: Type): [Type, (newState: Type) => void] { let state = initialState; function setState(newState: Type) { state = newState; }
return [state, setState]; }
const [count, setCount] = useState(100); const [message, setMessage] = useState("Hello World"); const [banners, setBanners] = useState<any[]>([]);
|
多个泛型参数
一个函数可以定义多个泛型参数
平时在开发中可能会看到一些常用的名称
T
:Type 的缩写,类型
K
、V
:key
和value
的缩写,键值对
E
:Element
的缩写,元素
O
:Object
的缩写,对象
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 };
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; } }
const p1 = new Point(10, 20); console.log(p1.x); const p2 = new Point("123", "321"); console.log(p2.x);
|
泛型约束
希望传入的类型有某些共性,但是这些共性可能不是在同一种类型中
- 比如
string
和array
都是有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; }
function getLength(arg: ILength) { return arg.length; }
const length1 = getLength("aaaa"); const length2 = getLength(["aaa", "bbb", "ccc"]); const length3 = getLength({ length: 100 });
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
| interface IKun { name: string; age: number; }
type IKunKeys = keyof IKun;
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
|
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: "北京市" };
|