泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。
软件工程的一个主要部分是构建组件,这些组件不仅具有定义明确且一致的 API,而且还可以重用。能够处理今天和明天的数据的组件将为你提供构建大型软件系统的最灵活的能力。
简单写法
1 2 3 4 5 6 7 8 9 10 11 // 不使用泛型 function getName01(name: string): string { return name } console.log(getName01('翠花')) // 翠花 // 使用泛型 function getName02<T>(name: T): T { return name } console.log(getName02<string>('翠花')) // 翠花
从上面的例子我们可以知道,如果使用了泛型,可以在调用该函数时再指定类型。比如我们在获取名字时,发现有些人并没有使用真实姓名,而是使用了 数字代号 作为名字,这时我们应该怎么解决呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 // 不使用泛型 function getName01(name: string | number): string | number { return name } console.log(getName01('翠花')) // 翠花 console.log(getName01(123)) // 123 // 使用泛型 function getName02<T>(name: T): T { return name } console.log(getName02<string>('翠花')) // 翠花 console.log(getName02<number>(123)) // 123
结论:使用泛型更灵活。
泛型函数
1 2 3 4 function getName03<T>(name: T): T { return name } console.log(getName03<string>('隔壁老王')) // 隔壁老王
注意:<T> 中的 T 可以是其他合法的字母,建议使用大写字母。
泛型接口
1 2 3 4 5 6 7 8 interface GetName04 { <T>(name: T): T } function getName05<T>(name: T): T { return name } let my_name: GetName04 = getName05 console.log(my_name('小红')) // 小红
复杂点的用法
1 2 3 4 5 6 7 8 9 interface GetName06 { <T>(name: T): T; <U>(age: U): U; } function getName07<T, U>(args: { name: T, age: U }) : { name: T, age: U } { return args } let my_name_age: GetName06 = getName07 console.log(my_name_age({ name: '小红', age: 18 })) // { name: '小红', age: 18 }
泛型类
1 2 3 4 5 6 7 8 9 10 11 class Add<T> { value: T; addFn: (a: T, b: T) => T } let add_a_b = new Add<number>() add_a_b.value = 666 console.log(add_a_b.value) // 666 add_a_b.addFn = (a, b) => { return a + b } console.log(add_a_b.addFn(2, 3)) // 5
泛型约束
先看一个例子
1 2 3 4 function getName08<T>(name: T): T { return `我是${name}` // 不能将类型“string”分配给类型“T”。 } console.log(getName08<string>('隔壁老王'))
我们在使用泛型函数时,想要返回 我是${name} 时会报错,这是因为该函数返回的结果是字符串,而 name 是 T 类型,也就是未知类型,这时就会产生冲突了,有的朋友为了减少麻烦,可能会简单粗暴使用 any ,如下:
1 2 3 4 function getName08<T>(name: T): any { return `我是${name}` } console.log(getName08<string>('隔壁老王')) // 我是隔壁老王
这确实可以解决问题,但是却失去了使用泛型的意义,这时最佳的解决方法是使用 泛型约束,如下:
1 2 3 4 5 6 7 interface Str { name: String } function getName08<T extends Str>(person: T): any { return `我是${person.name}呀` } console.log(getName08({ name: '隔壁老王'})) // 我是隔壁老王呀
使用 keyof 约束对象
我们可以声明受另一个类型参数约束的类型类型参数。如下,U (search) 继承并受到了 T (obj) 的约束,这确保我们不会获取到除 T (obj) 上不存在的属性,而是从 T (obj) 中获取其中的属性。
1 2 3 4 5 6 7 8 9 10 11 12 13 function getObj<T, U extends keyof T>(obj: T, search: U) { return obj[search] } const students = { 1: { name: '张三', age: 16 }, 2: { name: '李四', age: 17 }, 3: { name: '王五', age: 18 }, 4: { name: '赵六', age: 19 }, 5: { name: '老七', age: 20 } } console.log(getObj(students, 3)) // { name: '王五', age: 18 } console.log(getObj(students, 5)) // { name: '老七', age: 20 } console.log(getObj(students, 6)) // 类型“6”的参数不能赋给类型“2 | 3 | 1 | 4 | 5”的参数。