TypeScript 学习笔记(五):泛型

一、泛型是什么?有什么作用

软件工程中,我们不仅要创建一致的定义良好的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 ,泛型是具有当前指定的属性写法上 < T extends xx >
  2. 注意泛型约束是约束泛型的 在<> 这里写

1. 泛型约束

不知道类型就会报错,所以需要对参数类型进行约束

 function res<T>(val:T):T {
    
    
    console.log(val.length)
    return val
}
res(<string>"zhangsan") // 报错 类型“T”上不存在属性“length”
//这种写法也可以的哦
//res<string>("zhangsan")

先用 interface 声明一个变量类型,让类型变量 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. 泛型约束结合索引类型的使用

  1. 看下面案例想 获取对象 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]
}

getVal({
    
    name: 'lisi',age: 12},'age'); // 12
getVal({
    
    name: 'lisi',age: 12},'name'); // lisi
  • 使用泛型

    利用索引类型 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

四、常用的泛型内置工具类型

  1. Partial< Type >
  2. Readonly< Type >
  3. Pick< Type, Keys >
  4. Record< Keys, Type >
  5. Exclude < UnionType, ExcludedMembers >
  6. ReturnType < Type >

1. Partial < Type >

用来构造 (创建) 一个类型, 将 Type 的所有属性设置为可选

源码:

type Partial<T> = {
    
     [P in keyof T]?: T[P] };

keyof 产生枚举类型, in 使用枚举类型遍历;
上面语句的意思是 keyof T 拿到 T 所有属性名,然后 in 进行遍历,将值赋给 P ,最后 T[P] 取得相应属性的值。

  • 案例一:
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 >

用来构造一个类型, 将 Type 的所有属性都设置为 Readonly (只读 )

源码:

type Readonly<T> = {
    
     
 readonly [P in keyof T]: T[P];
};
  • 案例一:
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,Keys >

从 Type 中选择一组属性来构造新类型,多个 key 时 中间用 |

源码:

type Pick<T, K extends keyof T> = {
    
      
    [P in K]: T[P];
};
  • 案例一:
//两个变量表示: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] // res[key] 属性名, obj[key] 属性值
    })
    return res
}
const nameAndAddress = pick(info5, ['name', 'address']) 
console.log(nameAndAddress) // {name: 'lison', address: 'beijing'}

4. Record < Keys,Type >

构造一个对象类型,属性键为 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 < UnionType, ExcludedMembers >

Exclude<T,U>的作用是将某个类型中属于另一个类型的属性移除掉,示例:

type T0 = Exclude<"a" | "b" | "c", "a">; // "b" | "c"
const t:T0 ='b';

6. ReturnType < Type >

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

7. Required

源码:

type Required<T> = {
    
        
    [P in keyof T]-?: T[P] // 其中 -? 是代表移除 ? 这个 modifier 的标识 
};

五、下一节 :

TypeScript 学习笔记(六):索引签名类型、映射类型

猜你喜欢

转载自blog.csdn.net/renlimin1/article/details/131768919