一 泛型是什么
在像 C# 和 Java 这样的语言中,可以使用泛型来创建可重用的组件,一个组件可以支持多种类型的数据。 这样用户就可以以自己的数据类型来使用组件
设计泛型的关键目的是在成员之间提供有意义的约束,这些成员可以是:类的实例成员、类的方法、函数参数和函数返回值
其实并不是只能定义一个类型变量,我们可以引入希望定义的任何数量的类型变量。比如我们引入一个新的类型变量 U,用于扩展我们定义的 identity 函数:
/* 应用在函数里 */
function identity <T, U>(value: T, message: U) : T {
console.log(message);
return value;
}
console.log(identity<Number, string>(68, "Semlinker"));
编译器 自动选择类型
function identity <T, U>(value: T, message: U) : T {
console.log(message);
return value;
}
console.log(identity(68, "Semlinker"));
二 泛型接口
interface Person<T, N> {
name: T;
value: N
}
function identity<T, U> (value: T, message: U): Identities<T, U> {
console.log(value + ": " + typeof (value));
console.log(message + ": " + typeof (message));
let identities: Identities<T, U> = {
value,
message
};
return identities;
}
console.log(identity(68, "Semlinker"));
三 泛型类
在类名后面使用, 定义任意多个类型变量
interface GenericInterface<U> {
value: U
getIdentity: () => U
}
class IdentityClass<T> implements GenericInterface<T> {
value: T
constructor(value: T) {
this.value = value
}
getIdentity(): T {
return this.value
}
}
const myNumberClass = new IdentityClass<Number>(68);
console.log(myNumberClass.getIdentity()); // 68
const myStringClass = new IdentityClass<string>("Semlinker!");
console.log(myStringClass.getIdentity()); // Semlinker!
具体什么时候使用泛型类?
- 当你的函数、接口或类将处理多种数据类型时;
- 当函数、接口或类在多个地方使用该数据类型时。
四 泛型约束 extends
T extends Length 用于告诉编译器,我们支持已经实现 Length 接口的任何类型
之后,当我们使用不含有 length 属性的对象作为参数调用 identity 函数时, TypeScript 会提示相关的错误信息
我们还可以使用 , 号来分隔多种约束类型,比如:<T extends Length, Type2, Type3>
/* 1 */
interface Length {
length: number;
}
function identity<T extends Length>(arg: T): T {
console.log(arg.length); // 可以获取length属性
return arg;
}
/* 2 设置为数组类型也可以解决上面的问题 */
function identity<T>(arg: T[]): T[] {
console.log(arg.length);
return arg;
}
// or
function identity<T>(arg: Array<T>): Array<T> {
console.log(arg.length);
return arg;
}
检查对象上的键是否存在
keyof 操作符是在 TypeScript 2.1 版本引入的,该操作符可以用于获取某种类型的所有键,其返回类型是联合类型
interface Person {
name: string;
age: number;
location: string;
}
type K1 = keyof Person; // "name" | "age" | "location"
type K2 = keyof Person[]; // number | "length" | "push" | "concat" | ...
type K3 = keyof { [x: string]: Person }; // string | number
结合 keyof 使用泛型
function myFun<T, K extends keyof T> (obj: T, key: K): T[K] {
return obj[key];
}
在以上的 getProperty 函数中,我们通过 K extends keyof T 确保参数 key 一定是对象中含有的键,这样就不会发生运行时错误。这是一个类型安全的解决方案,与简单调用 let value = obj[key]; 不同
五 泛型参数默认类型
为泛型中的类型参数,指定默认类型
interface A<T=string> {
name: T;
}
const strA: A = { name: "Semlinker" };
const numB: A<number> = { name: 101 };
六 泛型条件类型 T extends U ? X : Y
条件类型,会以一个条件表达式进行类型关系检测
下面这个代码的意思是:
如果 T能够赋值给U,那么类型是X,否则是Y
在条件类型表达式中,我们通常还会结合 infer 关键字,实现类型抽取
T extends U ? X : Y
在上面示例中,当类型 T 满足 T extends Dictionary 约束时, 我们会使用 infer 关键字声明了一个类型变量 V,并返回该类型,否则返回 never 类型。
any 也不可以赋值给 never
interface Dictionary<T = any> {
[key: string]: T;
}
type StrDict = Dictionary<string>
type DictMember<T> = T extends Dictionary<infer V> ? V : never
type StrDictMember = DictMember<StrDict> // string
利用条件类型和 infer 关键字,我们还可以方便地实现获取 Promise 对象的返回值类型
async function stringPromise() {
return "Hello, Semlinker!";
}
interface Person {
name: string;
age: number;
}
async function personPromise() {
return { name: "Semlinker", age: 30 } as Person;
}
type PromiseType<T> = (args: any[]) => Promise<T>;
type UnPromisify<T> = T extends PromiseType<infer U> ? U : never;
type extractStringPromise = UnPromisify<typeof stringPromise>; // string
type extractPersonPromise = UnPromisify<typeof personPromise>; // Person
七 泛型工具类型
为了方便开发者 TypeScript 内置了一些常用的工具类型,比如 Partial、Required、Readonly、Record 和 ReturnType 等
/* Partial */
type Partial<T> = {
[P in keyof T]?: T[P];
};
/* Record 将K中所有属性的值,设置为T */
type Record<K extends keyof any, T> = {
[P in K]: T;
};
interface PageInfo {
title: string;
}
type Page = "home" | "about" | "contact";
const x: Record<Page, PageInfo> = {
about: { title: "about" },
contact: { title: "contact" },
home: { title: "home" }
};
/*
Pick
Pick<T, K extends keyof T> 的作用是将某个类型中的子属性挑出来,
变成包含这个类型部分属性的子类型。
*/
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
}
interface Todo {
title: string;
description: string;
completed: boolean;
}
type TodoPreview = Pick<Todo, "title" | "completed">;
const todo: TodoPreview = {
title: "Clean room",
completed: false
};
/*
Exclude
Exclude<T, U> 的作用是将某个类型中属于另一个的类型移除掉。
*/
// 如果 T 能赋值给 U 类型的话,那么就会返回 never 类型,否则返回 T 类型。
// 最终实现的效果就是将 T 中某些属于 U 的类型移除掉
type Exclude<T, U> = T extends U ? never : T;
// demo
type T0 = Exclude<"a" | "b" | "c", "a">; // "b" | "c"
type T1 = Exclude<"a" | "b" | "c", "a" | "b">; // "c"
type T2 = Exclude<string | number | (() => void), Function>; // string | number
/*
ReturnType
用于获取函数T的返回类型
*/
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer U ? U : any;
// demo
type T0 = ReturnType<() => string>; // string
type T1 = ReturnType<(s: string) => void>; // void
type T2 = ReturnType<<T>() => T>; // {}
type T3 = ReturnType<<T extends U, U extends number[]>() => T>; // number[]
type T4 = ReturnType<any>; // any
type T5 = ReturnType<never>; // any
type T6 = ReturnType<string>; // Error
type T7 = ReturnType<Function>; // Error
八 使用泛型创建对象
构造签名
interface Point {
new (x: number, y: number): Point;
}
class Point2D implements Point {
readonly x: number;
readonly y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
}
function newPoint(
pointConstructor: PointConstructor,
x: number,
y: number
): Point {
return new pointConstructor(x, y);
}
const point: Point = newPoint(Point2D, 1, 2);
使用泛型创建对象
class GenericCreator<T> {
create<T>(c: { new (): T }): T {
return new c();
}
}
create<T>(c: { new(a: number): T; }, num: number): T {
return new c(num);
}
const creator1 = new GenericCreator<FirstClass>();
const firstClass: FirstClass = creator1.create(FirstClass);
const creator2 = new GenericCreator<SecondClass>();
const secondClass: SecondClass = creator2.create(SecondClass);