软件工程中,我们不仅要创建一致的定义良好的 API,同时也要考虑可重用性。 组件不仅能够支持当前的数据类型,同时也能支持未来的数据类型,这在创建大型系统时为你提供了十分灵活的功能。
在像 C# 和 Java 这样的语言中,可以使用泛型来创建可重用的组件,一个组件可以支持多种类型的数据。 这样用户就可以以自己的数据类型来使用组件。
设计泛型的关键目的是在成员之间提供有意义的约束,这些成员可以是:类的实例成员、类的方法、函数参数和函数返回值。
泛型(Generics)是允许同一个函数接受不同类型参数的一种模板。相比于使用 any 类型,使用泛型来创建可复用的组件要更好,因为泛型会保留参数类型。
12.1 泛型接口
interface GenericIdentityFn<T> {
(arg: T): T;
}
函数泛性 generic
function add<T>(a: T, b: T) {
return `${a}${b}`
}
const res = add<string>('a', 'b')
console.log(res) // ab
function map<T>(params: T[]) {
return params
}
const res = map<string>(['123'])
console.log(res) // [ "123" ]
// 多个泛型
function add<T, P>(a: T, b: P) {
return `${a}${b}`
}
const res = add<string, number>('a', 2)
console.log(res) // a2
// 指定返回类型
function add<T>(a: T, b: T): T {
return `${a}${b}`
}
const res = add<string>('a', 'b')
console.log(res) // ab
12.2 泛型类
🌰 1
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
}
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function (x, y) {
return x + y;
};
🌰 2
class DataManager {
constructor(private data: string[]) {}
getItem(index: number): string {
return this.data[index]
}
}
const data = new DataManager(['6'])
const res = data.getItem(0)
console.log(res) // 6
// 当传入值是多种类型时
class DataManager<T> {
constructor(private data: T[]) {}
getItem(index: number): T {
return this.data[index]
}
}
const data = new DataManager<string>(['6'])
const res = data.getItem(0)
console.log(res)
更多🌰
interface Item {
name: string
}
class DataManager<T extends Item> {
constructor(private data: T[]) {}
getItem(index: number): string {
return this.data[index].name
}
}
const data = new DataManager([
{
name: 'Jak'
}
])
const res = data.getItem(0)
console.log(res) // Jack
指定泛型类型
class DataManager<T extends number | string> {
constructor(private data: T[]) {}
getItem(index: number): T {
return this.data[index]
}
}
const data = new DataManager<string>(['6'])
const res = data.getItem(0)
console.log(res) // 6
泛型作为一个具体的类型注解
function hello<T>(params: T) {
return params
}
const func: <T>(param: T) => T = hello
12.3 泛型变量
对刚接触 TypeScript 泛型的小伙伴来说,看到 T 和 E,还有 K 和 V 这些泛型变量时,估计会一脸懵逼。其实这些大写字母并没有什么本质的区别,只不过是一个约定好的规范而已。也就是说使用大写字母 A-Z 定义的类型变量都属于泛型,把 T 换成 A,也是一样的。下面我们介绍一下一些常见泛型变量代表的意思:
- T(Type):表示一个 TypeScript 类型
- K(Key):表示对象中的键类型
- V(Value):表示对象中的值类型
- E(Element):表示元素类型
12.4 泛型工具类型
为了方便开发者 TypeScript 内置了一些常用的工具类型,比如 Partial、Required、Readonly、Record 和 ReturnType 等。出于篇幅考虑,这里我们只简单介绍 Partial 工具类型。不过在具体介绍之前,我们得先介绍一些相关的基础知识,方便读者自行学习其它的工具类型。
1.typeof
在 TypeScript 中,typeof
操作符可以用来获取一个变量声明或对象的类型。
2.keyofinterface Person {
name: string;
age: number;
}
const sem: Person = { name: 'semlinker', age: 30 };
type Sem= typeof sem; // -> Person
function toArray(x: number): Array<number> {
return [x];
}
type Func = typeof toArray; // -> (x: number) => number[]
keyof
操作符可以用来一个对象中的所有 key 值:
3.ininterface Person {
name: string;
age: number;
}
type K1 = keyof Person; // "name" | "age"
type K2 = keyof Person[]; // "length" | "toString" | "pop" | "push" | "concat" | "join"
type K3 = keyof { [x: string]: Person }; // string | number
in
用来遍历枚举类型: ``` type Keys = “a” | “b” | “c” type Obj = {
} // -> { a: any, b: any, c: any }
**4.infer**<br />在条件类型语句中,可以用 `infer` 声明一个类型变量并且对它进行使用。
type ReturnType
以上代码中 `infer R` 就是声明一个变量来承载传入函数签名的返回值类型,简单说就是用它取到函数返回值的类型方便之后使用。<br />**5.extends**<br />有时候我们定义的泛型不想过于灵活或者说想继承某些类等,可以通过 extends 关键字添加泛型约束。
interface ILengthwise {
length: number;
}
function loggingIdentity
现在这个泛型函数被定义了约束,因此它不再是适用于任意类型:
loggingIdentity(3); // Error, number doesn’t have a .length property
这时我们需要传入符合约束类型的值,必须包含必须的属性:
loggingIdentity({length: 10, value: 3});
**6.Partial**<br />`Partial<T>` 的作用就是将某个类型里的属性全部变为可选项 `?`。<br />**定义:**
/**
- node_modules/typescript/lib/lib.es5.d.ts
- Make all properties in T optional
*/
type Partial
= { [P in keyof T]?: T[P]; };
interface Todo { title: string; description: string; } function updateTodo(todo: Todo, fieldsToUpdate: Partial在以上代码中,首先通过 `keyof T` 拿到 `T` 的所有属性名,然后使用 `in` 进行遍历,将值赋给 `P`,最后通过 `T[P]` 取得相应的属性值。中间的 `?` 号,用于将所有属性变为可选。<br />**示例:**
) { return { …todo, …fieldsToUpdate }; } const todo1 = { title: “organize desk”, description: “clear clutter”, }; const todo2 = updateTodo(todo1, { description: “throw out trash”, });
{ title?: string | undefined; description?: string | undefined; } ```在上面的 `updateTodo` 方法中,我们利用 `Partial<T>` 工具类型,定义 `fieldsToUpdate` 的类型为 `Partial<Todo>`,即:
泛型中keyof语法的使用
类型可以是字符串
type NAME = 'name'
const a: NAME = 'hello' // error
const b: NAME = 'name'
index.ts
interface Person {
name: string;
age: number;
gender: string;
}
class Teacher {
constructor(private info: Person) {}
getInfo<T extends keyof Person>(key: T): Person[T] {
// key: string 无法保护key是有效
return this.info[key]
}
}
const teacher = new Teacher({
name: "Jack",
age: 28,
gender: "male",
})
const test = teacher.getInfo('name')
console.log(test)