一、泛型是什么?有什么作用
软件工程中,我们不仅要创建一致的定义良好的API,同时也要考虑可重用性。 组件不仅能够支持当前的数据类型,同时也能支持未来的数据类型,这在创建大型系统时为你提供了十分灵活的功能。
在像C#和Java这样的语言中,可以使用泛型来创建可重用的组件,一个组件可以支持多种类型的数据。 这样用户就可以以自己的数据类型来使用组件。
通俗理解:泛型就是解决类、接口、方法的复用性、以及对不特定数据类型的支持。
泛型是什么呢?它可以说是一种类型占位符,也可以说是类型变量,需要注意的是它一种特殊的变量,只用于表示类型而不是值。我们在定义函数、接口或类的时候,不预先指定具体类型,而是在使用的时候再指定类型,先站住位置再说,保证了输入输出保持一致的问题。
这里举个例子说明为什么要使用泛型。我们写一个函数实现返回传递参数的值,并且打印这个值,参数类型为 string
,返回值类型也是string
,保证输入输出保持一致。
function res(val:string):string {
console.log(val)
return val
}
用any
来定义变量类型,如下:
function res(val:any):any {
console.log(val)
return val
}
注意:any 为任意类型,不能保证输入输出保持一致,比如参数类型是 string,返回的却是 number,所以最好不要用 any。
二、具体用法
1. 常用的泛型变量
T (Type) 表示类型;
K (Key) 表示对象中键的类型;
V (Value) 表示对象中值的类型;
U:表示对象中的键类型;
E (Element) 表示元素类型。
我们可以用一个类型变量来传递参数类型和返回值类型:
function res<T>(val:T):T {
console.log(val)
return val
}
res(<string>"zhangsan")
2. 泛型类
泛型类可以支持不特定的数据类型,要求传入的参数和返回的参数必须一致,T表示泛型,具体什么类型是调用这个方法的时候决定的。
- 案例一:
// 在代码中存在一个问题,它允许你向队列中添加任何类型的数据,当然,当数据被弹出
// 队列时,也可以是任意类型。在上面的示例中,看起来人们可以向队列中添加string 类型的数据
// 但是那么在使用的过程中,就会出现我们无法捕捉到的错误,
class Queue {
private data: unknown[] = []
push(item: unknown) {
return this.data.push(item)
}
pop() {
return this.data.shift()
}
}
const queue = new Queue()
queue.push(1)
queue.push('str')
class Queue<T> {
private data: T[] = []
push(item: T) {
return this.data.push(item)
}
pop() {
return this.data.shift()
}
}
const queue = new Queue<number>()
queue.push(1)
queue.pop()
- 案例二:
class Memory<S> {
store: S
constructor(store: S) {
this.store = store
}
set(store: S) {
this.store = store
}
get() {
return this.store
}
}
const numMemory = new Memory<number>(1) // <number> 可缺省
const getNumMemory = numMemory.get() // 类型是 number
numMemory.set(2) // 只能写入 number 类型
const strMemory = new Memory('') // 缺省 <string>
const getStrMemory = strMemory.get() // 类型是 string
strMemory.set('string') // 只能写入 string 类型
3. 泛型接口
interface GetArray<T> {
(arg: T, times: number): T[],
array: T[]
}
//泛型和 interface
interface KeyPair<T, U> {
key: T;
value: U;
}
let kp1: KeyPair<number, string> = { key: 1, value: "str"}
let kp2: KeyPair<string, number> = { key: "str", value: 123}
interface ReturnItemFn<T> {
(para: T): T
}
const returnItem: ReturnItemFn<number> = para => para //const returnItem: ReturnItemFn<number> = (para: any) => para
interface Class{
<T,U>(name:T,score:U):T
}
let func = function <T,U>(name:T,score:U):T{
return name + ':'+ score
}
func('zhangsan',3)//编译器自动识别参数类型,作为泛型的类型
4. 泛型类接口
//定义操作数据库的泛型类
class MysqlDb<T>{
add(info: T): boolean {
console.log(info);
return true;
}
}
//想给User表增加数据,定义一个User类和数据库进行映射
class User {
username: string | undefined;
pasword: string | undefined;
}
var user = new User();
user.username = "张三";
user.pasword = "123456";
var md1 = new MysqlDb<User>();
md1.add(user);
//想给ArticleCate增加数据,定义一个ArticleCate类和数据库进行映射
class ArticleCate {
title: string | undefined;
desc: string | undefined;
status: number | undefined;
constructor(params: {
title: string | undefined,
desc: string | undefined,
status?: number | undefined
}) {
this.title = params.title;
this.desc = params.desc;
this.status = params.status;
}
}
var article = new ArticleCate({
title: "这是标题",
desc: "这是描述",
status: 1
});
var md2 = new MysqlDb<ArticleCate>();
md2.add(article);
5. 泛型函数
function test <T> (arg:T):T{
console.log(arg);
return arg;
}
test<number>(111);// 返回值是number类型的 111
test<string | boolean>('hahaha')//返回值是string类型的 hahaha
test<string | boolean>(true);//返回值是布尔类型的 true
使用方式类似于函数传参,传什么数据类型,T就表示什么数据类型, 使用表示,T也可以换成任意字符串。
三、泛型约束
1.泛型现在似乎可以是任何类型,但实际开发可能往往不是任意类型,需要给以一个范围,这种就叫’泛型约束’关键字(‘extends’)
泛型是具有当前指定的属性写法上’’
2.注意泛型约束是约束泛型的 在<> 这里写
1. 泛型约束
不知道类型就会报错,所以需要对参数类型进行约束
function res<T>(val:T):T {
console.log(val.length)
return val
}
res(<string>"zhangsan") // 报错 类型“T”上不存在属性“length”
//这种写法也可以的哦
//res<string>("zhangsan")
先用 type 声明一个变量类型,让类型变量 T 继承接口 Class :
interface Class{
name:string,
age:number
}
function result<T extends Class>(val:T):T {
console.log(val.name)
return val
}
result({name:"zhangsan",age:10})
//如果参数中不写age的话,就会报错
//类型“{ name: string; }”的参数不能赋给类型“Class”的参数。
//类型 "{ name: string; }" 中缺少属性 "age",但类型 "Class" 中需要该属性。
result({name:"zhangsan"})
interface ValueWithLength {
length: number
}
const getArray = <T extends ValueWithLength>(arg: T, times): T[] => {
return new Array(times).fill(arg)
}
getArray([1, 2], 3)
getArray('123', 3)
getArray({
length: 2,
}, 3)
getArray(1, 3) // 报错 数字类型没有length
防止思维定式
function getExcludeProp<T extends {props:string}>(obj:T){
return obj
}
// getExcludeProp({name:'w'}) // 报错
getExcludeProp({name:'w',props:'w'})
2. 泛型约束结合索引类型的使用
- 看下面案例想 获取对象value 输出出来
type Info = {
name:string
age:number
}
function getVal(obj:Info, key:any) {
return obj[key] // 报错
}
- 正确写法可以利用keyof 吧传入的对象的属性类型取出生成一个联合类型:
type Info = {
name:string
age:number
}
function getVal(obj:Info, key:keyof Info) {
return obj[key]
}
- 使用泛型
1.利用’索引类型 keyof T 把传入的对象的属性类型取出生成一个联合类型’,再用’extends 做约束’
// 注意泛型约束是约束泛型的 在<> 这里写
type GetVal = <T extends object, K extends keyof T>(obj: T, key: K) => string
function getVal(obj: any, key: any): GetVal {
return obj[key]
}
getVal({ name: 'w' }, 'name')
3. 多重约束
interface FirstInterface {
doSomething(): number
}
interface SecondInterface {
doSomethingElse(): string
}
// // interface ChildInterface extends FirstInterface, SecondInterface {}
二者等同
class Demo<T extends FirstInterface & SecondInterface> {
private genericProperty: T
useT() {
this.genericProperty.doSomething() // ok
this.genericProperty.doSomethingElse() // ok
}
}
interface ValueWithLength {
length: number
}
const getArray = <T extends ValueWithLength>(arg: T, times): T[] => {
return new Array(times).fill(arg)
}
getArray([1, 2], 3)
getArray('123', 3)
getArray({
length: 2,
}, 3)
getArray(1, 3) // 报错 数字类型没有length
四、常用的泛型工具类型
- Partial
- Readonly
- Pick<Type, Keys>
- Record<Keys, Type>
1. Partial
用来构造 (创建) 一个类型, 将 Type 的所有属性设置为可选
- 案例一:
interface Props{
id:number,
name:string
}
type PartialProps = Partial<Props>
使用后 PartialProps里的所有属性都变成可选 即:
type PartialProps = {
id?: number | undefined;
name?: string | undefined;
}
- 案例二:
partial
的作用就是将某个类型中的属性全部变为可选项?
interface Person {
name:string;
age:number;
}
function student<T extends Person>(arg: Partial<T>):Partial<T> {
return arg;
}
2. Readonly
用来构造一个类型, 将 Type 的所有属性都设置为 readonly (只读 )
- 案例一:
interface Props{
id:number,
name:string
}
type ReadonlyProps = Readonly<Props>
let props : ReadonlyProps = {id:1,name:'xxx'}
// props.name='w3f' 这句话 此时会报错
使用Readonly后,对象里面的属性只可读,不可以改变。此时ReadonlyProps为:
type ReadonlyProps = {
readonly id: number;
readonly name: string;
}
- 案例二:
3. Pick
从 Type 中选择一组属性来构造新类型
- 案例一:
//两个变量表示:1)表示谁的属性 2)表示选择哪一个属性,此属性只能是前面第一个变量传入的类型变量中的属性
interface Props2{
id:number,
name:string,
age:number
}
type PickProps = Pick<Props2,'id'|'age'> //这表示 构造出来的新类型PickProps 只有id age两个属性类型
表示在旧的类型中选择一部分属性,讲选择的属性来重新构造成一个新的类型。此时PickProps为:
type PickProps = {
id: number;
age: number;
}
- 案例二:
interface Info {
name:string,
age:number,
address:string
}
// 选择只使用 字段 这里选择的是name 和age 字段
const a:Pick<Info,'name'|'age'> = {
name:'w',
age:12
}
// 当方法需要返回是一个对象时候
const info5 = {
name: 'lison',
age: 18,
address: 'beijing',
}
// 返回的是一个对象 用pick 来做了指定
function pick<T, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> {
const res: any = {}
keys.map((key) => {
res[key] = obj[key]
})
return res
}
const nameAndAddress = pick(info5, ['name', 'address'])
console.log(nameAndAddress)
4. Record
构造一个对象类型,属性键为 Keys ,属性类型为 Type。
- 案例一:
//两个变量表示:1)表示对象有哪些属性 2)表示对象属性的类型
type RecordObj = Record<'a'|'b'|'c',string> //此处表示 此类型变量有a b c三个键,并且三个的属性值都是string类型
let obj :RecordObj = {
a:'1',
b:'2',
c:'3'
}
type RecordObj = {
a: string;
b: string;
c: string;
}
- 案例二:
Record<K extends keyof any, T>
的作用是将K
中所有的属性转换为T
类型:
interface PageInfo {
title: string
}
type Page = 'home'|'about'|'other';
const x: Record<Page, PageInfo> = {
home: { title: "xxx" },
about: { title: "aaa" },
other: { title: "ccc" },
};
5. Exclude
Exclude<T,U>
的作用是将某个类型中属于另一个类型的属性移除掉,示例:
type T0 = Exclude<"a" | "b" | "c", "a">; // "b" | "c"
const t:T0 ='b';
6. ReturnType
returnType
的作用是用于获取函数T的返回类型,示例:
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