TypeScript(六)泛型

泛型是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。一般用于解决类、方法、接口的复用性,以及对不特定数据类型的支持(类型校验)。

创建一个函数,要求函数的返回值就是函数的参数,参数类型是任意的。可以通过如下方式实现:

function echo(val: any): any {
  return val
}

以上使用any类型有几个不足之处:首先any类型ts会放弃类型检查,失去使用ts的初衷及优势;其次,既然返回值是any类型,并不能准确的定义返回值的类型。此时泛型就派上用场了。

1. 泛型函数

实现一个函数,函数接收两个参数,返回值是参数1为length,参数2为元素组成的数组。

function createArray<T>(length: number, value: T): Array<T> {
  const result: T[] = []
  for (let i = 0; i < length; i++) {
      result[i] = value
  }
  return result
}

以上就是一个泛型函数, T 代表任意类型。

泛型函数调用方式:

  1. 传入所有参数,包括类型参数
createArray<string>(3, 'x') // ['x', 'x', 'x']
  1. 利用类型推论,直接传入参数
createArray(3, 'x')

多个类型参数

实现一个函数,参数是长度为2、元素为任意类型的元组,返回交换位置后的元组。

function exchange<T, U>(tuple: [T, U]): [U, T] {
    return [tuple[1], tuple[0]]
}

exchange([7, 'sina']); // ['sina', 7]

通过类型别名定义泛型函数

type IGetArr<T> = (val: T, length: number) => T[]
const getArr: IGetArr<number> = (val: number, length: number = 3): number[] => {
  return Array(length).fill(val)
}
getArr('4', 5)

2. 泛型约束

在泛型函数内部使用泛型变量时,由于我们预先并不知道它是哪种类型,所以不能随意操作其属性和方法(比如:获取泛型变量的length),但 T 不一定包含该属性,所以编译器会报错。

function getLength<T>(arg: T): T {
  console.log(arg.length) // Property 'length' does not exist on type 'T'.
  return arg
}

此时就需要对泛型变量进行约束,要求其必须包含length属性,这种操作我们称之为泛型约束。

定义接口来描述约束条件,并用 extends 关键字实现约束。

如下泛型函数的参数必须具有length属性:

interface restrainLength {
  length: number
}

function getLength<T extends restrainLength>(arg: T): T {
  console.log(arg.length) 
  return arg
}

多个参数时也可以在泛型约束中使用类型参数

多个参数的泛型函数,一个类型参数被另一类型参数所约束。

定义一个函数, 接受两个参数 第一个是对象 obj,第二个是第一参数对象中的key,返回 obj[key]。

使用 keyof 关键字约束。

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

const obj = { a: 1, b: 2, c: 3 }
getValue(obj, 'a')
getValue(obj, 'd') //  Argument of type '"d"' is not assignable to parameter of type '"a" | "b" | "c"'.
keyof 索引类型查询操作符

对应任何类型T,keyof T的结果为该类型上所有公共属性名的联合:

interface Eg1 {
  name: string,
  readonly age: number,
}
// T1的类型实则是 "name" | "age"
type T1 = keyof Eg1

class Eg2 {
  private name: string;
  public readonly age: number;
  protected home: string;
}
// T2实则被约束为 "age"
// 因为name和home不是公有属性,所以不能被keyof获取到
type T2 = keyof Eg2
interface Eg1 {
  name: string,
  readonly age: number,
}

interface Eg2 {
  sex: string
}
// T1的类型实则是 "name" | "age" | { sex: string }
type T1 = keyof Eg1 | Eg2

let a: T1 = "name"; // OK
let b: T1 = "age"; // OK
let c: T1 = { // OK
  sex: "男"
}

注意:keyof any 的结果为 string | number | symbol,因为对象的key常见的类型就是这三种

TypeScript 2.8 作用于交叉类型的keyof被转换成作用于交叉成员的keyof的联合。 换句话说,keyof (A & B)会被转换成keyof A | keyof B。 这个改动应该能够解决keyof表达式推断不一致的问题。

type A = { a: string };
type B = { b: string };
type T1 = keyof (A & B);  // "a" | "b"
type T2<T> = keyof (T & B);  // keyof T | "b"
type T3<U> = keyof (A & U);  // "a" | keyof U
type T4<T, U> = keyof (T & U);  // keyof T | keyof U
type T5 = T2<A>;  // "a" | "b"
type T6 = T3<B>;  // "a" | "b"
type T7 = T4<A, B>;  // "a" | "b"

3. 泛型接口

可以通过接口的方式定义函数需要遵循的约束和规范。

interface IFoo {
  (name: string): string
}

let getInfo: IFoo = function(name: string): string{
  return `the name is ${name}` 
}

这种方式不具有普适性,name的类型被限制死了。可以通过含有泛型的接口定义函数,如下:

interface IFoo {
  <T>(length: number, val: T): Array<T>
} 
const getArr: IFoo = function<T>(length: number, val: T): Array<T> {
  return Array(length).fill(val)
}
console.log(getArr(3, 'sian')) // ["sian", "sian", "sian"]

把泛型参数提前到接口名上,就可以知道使用的具体是哪个泛型类型,这样接口里的其他成员也能知道这个参数的类型。

interface IFoo<T> {
  (length: number, val: T): Array<T>;
}

const getFoo: IFoo<string> = function (
    length: number,
    val: string
  ): [string, number] {
    return [val, length]
  }

function getArr<T>(length: number, val: T): Array<T> {
  return Array(length).fill(val)
}

const getFoo: IFoo<string> = getArr
getFoo(3, 'sina')  // ['sina', 'sina', 'sina']

注意: 此时使用泛型接口时, 需要定义泛型的类型

4. 泛型类

与泛型接口类似,泛型也可以用于类的类型定义中,泛型类使用 <> 括起泛型类型,跟在类名后面:

class Test<T> {
  foo: T
  constructor(foo: T) {
    this.foo = foo
  }
  echo(val: T): T {
    return val
  }
}

const test = new Test<number>(12)
test.echo(3)

泛型参数的默认类型

可以为泛型中的类型参数指定默认类型。当使用泛型时没有在代码中直接指定类型参数,从实际值参数中也无法推测出时,这个默认类型就会起作用。

function createArray<T = string>(length: number, value: T): Array<T> {
  const result: T[] = []
  for (let i = 0; i < length; i++) {
      result[i] = value
  }
  return result
}
createArray<number>(3, 3)

在.tsx中使用尾随逗号

只有一个泛型参数时,尾随逗号是必须的。否则会出现编译错误。


image.png
const returnInArray = <T,>(value: T): T[] => {
  return [value];
};

const strArray = returnInArray<string>('hello');
const numArray = returnInArray<number>(360);

如果有多种类型,则不必使用尾随逗号,例如:<T, Y>
如果使用了尾随逗号文件还报错,可以扩展一个空对象。

const carr = <T extends unknown>(val: T): T[] => {
    return [val]
}

carr<string>('3')
carr(4)

T extends unknown 约束不执行任何操作,并且仅当处理.tsx文件时出现语法错误时才需要该约束。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,099评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,828评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,540评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,848评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,971评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,132评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,193评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,934评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,376评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,687评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,846评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,537评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,175评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,887评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,134评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,674评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,741评论 2 351

推荐阅读更多精彩内容