一、泛型是什么
软件工程中,咱们不仅要创立统一的定义良好的 API,同时也要思考可重用性。组件不仅可能反对以后的数据类型,同时也能反对将来的数据类型,这在创立大型零碎时为你提供了非常灵便的性能。
在像 C# 和 Java 这样的语言中,能够应用 泛型 来创立可重用的组件,一个组件能够反对多种类型的数据。这样用户就能够以本人的数据类型来应用组件。
—— 官网文档介绍
官网文档说的有点晕,不过既然介绍提到了 Java,那就看看泛型在 Java 的解释:
Java 泛型是 J2 SE1.5 中引入的一个新个性,其本质是 参数化类型 ,也就是说所操作的数据类型被指定为一个参数(type parameter)这种参数类型能够用在 类、接口 和办法 的创立中,别离称为 泛型类 、 泛型接口 、 泛型办法。
—— 百度百科
对于参数咱们就比拟相熟了,在定义函数的时候写入形参,前面调用的时候再传入具体的实参;同样的,参数化类型也就是将原先的具体的类型当做一个参数来解决,在定义阶段就相当于定义函数时候的形参一样,没有指定的类型,只是相当于一个占位符的作用,而后在应用阶段的时候依据传入的类型来确定。
也就是说在泛型应用过程中,操作的数据类型被指定为一个参数,这种参数类型能够用在类、接口和办法中,也就被别离称为泛型类、泛型接口、泛型办法。
二、泛型怎么用
说完概念,来看看一个简略的泛型怎么创立。
- 泛型办法
function fn1<T>(value: T): T {return value}
const value = fn1<number>(3)
依照下面的了解,在定义 fn1
办法的时候,咱们写入了形参 value
和泛型 <T>
,T
被称为类型变量,它能够在函数内作为类型占位符应用,这里被指定为参数 value
和办法 fn1
的返回值的类型。
当咱们在调用办法的时候,fn1<number>(3)
就相当于是传入了函数的实参 3
以及类型变量的值 number
, 这是显式的设定了类型变量的值,不过通常在调用的时候能够隐式设定,也就是疏忽尖括号 fn1(3)
,编译器会主动去辨认类型变量与参数的关系,给类型变量赋值。
类型变量的数量是任意的,须要几个就定义几个,类型变量之间用逗号分隔 <T, ...>
:
function fn2<T, U>(value: T, message: U): [T, U] {return [value, message]
}
- 泛型接口
泛型还能够用在接口上,也就是泛型接口:
interface Fn<V, M> {
message: M
value: V
}
function fn3<T, U>(value: T, message: U): Fn<T, U> {
return {
message,
value
}
}
- 泛型类
泛型在形容类的时候就是泛型类:
class Queue {private data = []
push = item => this.data.push(item)
pop = () => this.data.shift()
}
下面的代码是队列的简略实现,如果要求队列元素都是 number
类型,显著这样写就是不足够的,因为当初并没有指定类型,TypeScript 并不会去查看。一个简略的批改办法就是再增加元素的时候限度类型:
class QueueNumber {private data = []
push = (item: number) => this.data.push(item)
pop = () => this.data.shift()
}
const numberQueue = new QueueNumber()
numberQueue.push(1)
numberQueue.push('34') // 谬误 提醒类型不统一
如果是须要两个队列,一个是 number
类型,一个是 string
类型,那该怎么搞?这时就须要泛型了:
class QueueNumber<T> {private data: T[] = []
push = (item: T) => this.data.push(item)
pop = () => this.data.shift()
}
const numberQueue = new Queue<number>()
numberQueue.push(2)
const stringQueue = new Queue<string>()
stringQueue.push('2')
三、泛型参数的默认类型
TypeScript 2.3 之后,能够给参数类型指定默认类型。
function createArray<T = string>(length: number, value: T): Array<T> {let result: T[] = []
for (let i = 0; i < length; i++) {result[i] = value
}
return result
}
四、泛型束缚与继承
当咱们应用泛型的时候,因为并没有确定的类型,所以不能随便的操作它的属性和办法,限度了咱们随便的操作对象导致的一些问题,这就是泛型的束缚作用。
function loggingIdentity<T>(arg: T): T {console.log(arg.length) // 谬误 类型“T”上不存在属性“length”return arg
}
能够定义一个接口来形容约束条件,通过继承 extends
这个接口实现:
泛型的继承和接口相似。
interface Lengthwise {length: number}
function loggingIdentity<T extends Lengthwise>(arg: T): T {console.log(arg.length)
return arg
}
多个类型参数状况下的束缚:
function copyKeysBad<T , U>(target: T, source: U): T {for (let key in source) {target[key] = source[key] // 谬误 类型 T 不存在类型 U 的属性
}
return target
}
// 类型 T 继承类型 U 保障 U 上不会呈现 T 中不存在的字段
function copyKeys<T extends U, U>(target: T, source: U): T {for (let key in source) {target[key] = (<T>source)[key]
}
return target
}
let x = {a: 1, b: 2, c: 3, d: 4}
copyKeys(x, { b: 10, d: 20})
五、泛型条件
在 TypeScript 2.8 中引入了条件类型,依据条件失去不同的类型。
// 相似三元运算
// 如果类型 T 是类型 U 的子集 那么类型是 X 否则是 Y
T extends U ? X : Y
泛型条件也能够嵌套,和 if
语句、三元运算一样,能够嵌套。
type TypeName<T> =
T extends string ? "string" :
T extends number ? "number" :
T extends boolean ? "boolean" :
T extends undefined ? "undefined" :
T extends Function ? "function" :
"object";
type T0 = TypeName<string> // "string"
type T1 = TypeName<"a"> // "string"
type T2 = TypeName<true> // "boolean"
type T3 = TypeName<() => void> // "function"
type T4 = TypeName<string[]> // "object"
分布式条件类型
条件类型还有一个个性:分布式条件类型。在联合联结类型应用时(extends
右边的联结类型),分布式条件类型会被主动散发成联结类型。
type newType = T extends U ? X : Y
// 如果 T 的类型是 A|B|C
// 会被解析为 (A extends U ? X : Y) | (B extends U ? X : Y) | (C extends U ? X : Y)
type T10 = TypeName<string | (() => void)> // "string" | "function"
type T12 = TypeName<string | string[] | undefined> // "string" | "object" | "undefined"
type T11 = TypeName<string[] | number[]> // "object"
分布式条件类型的前提是待查看的类型必须是裸类型(naked type parameter
),即没有被数组,元组或者函数包裹。
// naked type
type NakedType<T> = T extends boolean ? "yes" : "no"
type DistributedUsage = NakedType<number | boolean> // "yes" | "no"
// wrapped type
type WrappedType<T> = Array<T> extends Array<boolean> ? "yes" : "no"
type NonDistributedUsage = WrappedType<number | boolean> // "no"
infer
关键字
条件类型还能够在条件判断中申明泛型类型。通过应用 infer
关键字。
type FunctionReturnType<T> = T extends (...args: any[]) => infer R ? R : T
type Foo = FunctionReturnType<() => void> // void
type Bar = FunctionReturnType<(name: string) => string> // string
type Buz = FunctionReturnType<(name: string, other: string) => boolean> // boolean
type Str = FunctionReturnType<string> // string
infer R
就是在条件判断中申明的新的泛型,它会依据理论传入的泛型类型推断出该泛型的具体类型。
协变和逆变
对于逆变和协变的具体内容,这里先不具体开展,这两个概念也只是说到这里才刚意识,还没搞懂,前面会再具体深刻。这里先简略的理解一下。
协变就是让泛型接口能够承受一个更加具体的接口作为参数或返回值;逆变就是让接口的参数类型或返回值更加具体。
逆变就是对具体成员的输出参数进行一次类型转换,逆变就是对具体成员的输出参数进行一次类型转换。
// 在协变地位上,同一个类型变量的多个候选类型会被推断为联结类型
type Foo<T> = T extends {a: infer U, b: infer U} ? U : never;
type T10 = Foo<{a: string, b: string}>; // string
type T11 = Foo<{a: string, b: number}>; // string | number
// 在抗变地位上,同一个类型变量的多个候选类型会被推断为穿插类型
type Bar<T> = T extends {a: (x: infer U) => void, b: (x: infer U) => void } ? U : never;
type T20 = Bar<{a: (x: string) => void, b: (x: string) => void }>; // string
type T21 = Bar<{a: (x: string) => void, b: (x: number) => void }>; // string & number
六、内置罕用的泛型
Partial
Partial<T>
的作用就是将某个类型里的属性全副变为可选项 ?
type Partial<T> = {[P in keyof T]?: T[P]
}
Record
Record<K extends keyof any, T>
的作用是将 K
中所有的属性的值转化为 T
类型
type Record<K extends keyof any, T> = {[P in K]: T
}
Pick
Pick<T, K extends keyof T>
的作用是将某个类型中的子属性挑出来,变成蕴含这个类型局部属性的子类型
type Pick<T, K extends keyof T> = {[P in K]: T[P]
}
Exclude
Exclude<T, U>
的作用是将某个类型中属于另一个的类型移除掉
type Exclude<T, U> = T extends U ? never : T
ReturnType
ReturnType<T>
的作用是用于获取函数 T
的返回类型
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any
参考
TypeScript 入门教程
泛型 | 深刻了解 TypeScript)
一文读懂 TypeScript 泛型及利用(7.8K 字)