一文详解TypeScript泛型大法,学习泛型各种姿势

407 阅读3分钟

前言

typescript中,泛型允许您定义一个类型变量,作为一个特定类型的占位符,当你在使用再传递一个具体的类型进来。这是泛型的核心优势,它允许你定义一个灵活、可重用的类型,而不会牺牲类型安全性。

泛型函数

我们以一个普通的函数来小试牛刀

function example<T>(arg: T): T {
  return arg;
}

// 手动传递类型
let age = example<number>(18); // T = number
let me = example<string>('vilan'); // T = string

// 以下写法ts会自动推断类型
// let age = example(18); // T = number
// let me = example('vilan'); // T = string

在以上示例中T代表可以传递任意类型的参数,可以在调用方法时手动传递T类型参数,但是为了写法更加简洁,通常会选择通过传递的参数进行泛型T的类型推导,即不需要手动传递类型。

泛型约束

想要约束一个泛型的类型,可通过extends关键字行约束,这将把泛型的类型限制在你所指定的范围内。

参数类型约束

以下我们定义一个方法的参数为泛型并且还需要读取该参数中的某个属性

function example<T>(value: T) {
  if(value.length > 0) {
    // 处理长度大于0的逻辑
    // ...
  }
}

以上没有对泛型T进行约束,所有使用value.length时,将报错:类型“T”上不存在属性“length”;这时就需要对泛型进行约束。

type HasLength = {
  length: number;
}
function example<T extends HasLength>(value: T) {
  if(value.length > 0) {
    // 处理长度大于0的逻辑
    // ...
  }
}

// √能正常工作
example('3')
example([1,2,3])
example({a: 1, length: 2})
example({a: true, length: 2})

// ×不能正常工作
example(1)
example(true)

参数键名约束

当一个泛型参数为对象时,又需要访问该对象下的属性:

function getValue<T, K>(obj: T, key: K) {
  return obj[key]
}

以上读取obj[key]将会报错类型“K”无法用于索引类型“T”,这时我们需要将K的类型范围进行约束:

function getValue<T, K extends keyof T>(obj: T, key: K) {
  return obj[key]
}

const data = {
  name: 'vilan',
  age: 18
}

// √正常工作
const value = getValue(data, 'name')
const age = getValue(data, 'age')

// × 类型“"weight"”的参数不能赋给类型“"name" | "age"”的参数。
const weight = getValue(data, 'weight') 

以上通过K extends keyof T,将K的类型约束在了泛型T的键名范围

泛型接口和类型

interface Data<T, K, U> {
  one: T,
  two: K,
  three: U,
  name: string
}
// 或
// type Data<T, K, U> = {
//   one: T,
//   two: K,
//   three: U,
//   name: string
// }
let data1: Data<boolean, number> = { one: true, two: 2, }
let data2: Data<number, string> = { one: 1, two: "2" }
let data3: Data<string, string> = { one: '1', two: '2' }

以上通过interfacetype声明的类型,都可在类型名称后面加<>来指定传递的泛型参数。

泛型类

class Animal<T> {
  type: T
  constructor(type: T){
    this.type = type
  }
  getType(): T {
    return this.type
  }
}

const dog = new Animal('小狗')
const cat = new Animal(1)

类泛型和接口类型一样,可通过类名后面定义泛型,在类中进行使用。

泛型默认值

// 定义一个大多数情况下T为number的通用类型
interface MyArray<T = number> {
  data: T[];
}

// 不用传入泛型,自动使用默认类型
const arr: MyArray = {
  data: [1,2,3]
}

// 由于data: string[],默认类型不满足,需要手动传入类型
const arr2: MyArray<string> = {
  data: ['1', '2', '3']
}

结语

总之,泛型在对封装可重用组件时非常有用,可让你定义一个变量的类型为泛型,从而适应多种类型值,也能够正确地进行类型推导,保证方法的通用性而不丢失类型的安全性。