【TS】<T> 泛型

基本用法

  • 泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。
  • 设计泛型的关键目的是在成员之间提供有意义的约束,这些成员可以是:
1. 类的实例成员
2. 类的方法
3. 函数参数
4. 函数返回值

案例 1

  • 创造一个identity函数, 这个函数会返回任何传入它的 值
  • 不使用泛型(number版):
// 该函数对参数 arg 限制类型为 number,对返回值也限制类型为 number
function identity(arg: number): number {
    return arg;
}
console.log(identity(3)) // 3
console.log(identity("str")) // 报错
console.log(identity(null)) // 报错
  • 使用泛型(最终版):
// 我们使用类型变量T,它是一种特殊的变量,只用于表示类型而不是值。
function identity<T>(arg: T): T {
    return arg;
}
console.log(identity(3)) // 3
console.log(identity("str")) // "str"
console.log(identity(null)) // null
  • 结论:
1. 我们给identity添加了类型变量 <T>2. (arg: T)帮助我们捕获用户传入的类型(比如:number) 
3. 之后我们再次使用了 T 当做返回值类型。
4. 现在我们可以知道参数类型与返回值类型是相同的了。 这允许我们跟踪函数里使用的类型的信息。
5. 我们把这个版本的identity函数叫做泛型,因为它可以适用于多个类型。 
6. 不同于使用 any,它不会丢失信息,像第一个例子那像保持准确性,传入数值类型并返回数值类型。

使用泛型函数:

function identity<T>(arg: T): T {
    return arg;
}
// 第一种方法:传入所有的参数,包含类型参数
let output = identity<string>("myString");	// 输出类型为'string'

// 第二种方法:编译器会根据传入的参数自动地帮助我们确定T的类型(类型推论)
let output = identity("myString");	// 输出类型为'string'

案例 2

  • 首先,我们来实现一个函数 createArray,它可以创建一个指定长度的数组,同时将每一项都填充一个默认值:
// 使用数组泛型Array<any>来定义返回值的类型
// Array<any> 允许数组的每一项都为任意类型,并没有准确的定义返回值的类型
function createArray(length: number, value: any): Array<any> {
    let result = [];
    for (let i = 0; i < length; i++) {
        result[i] = value;
    }
    return result;
}

createArray(3, 'x'); // ['x', 'x', 'x']
  • 使用泛型使得数组中每一项都应该是输入的 value 的类型
// 在调用的时候,如果不指定它<T>具体的类型,也可以让类型推论自动推算出来:
function createArray<T>(length: number, value: T): Array<T> {
    let result: T[] = [];
    for (let i = 0; i < length; i++) {
        result[i] = value;
    }
    return result;
}

createArray<string>(3, 'x'); // ['x', 'x', 'x']

多个参数

  • 定义泛型的时候,可以一次定义多个类型参数:
function swap<T, U>(tuple: [T, U]): [U, T] {
    return [tuple[1], tuple[0]];
}
// 定义了一个 swap 函数,用来交换输入的元组。
swap([7, 'seven']); // ['seven', 7]

泛型约束

  • 使用泛型创建像identity这样的泛型函数时,编译器要求你在函数体必须正确的使用这个通用的类型。
  • 换句话说,你必须把这些参数当做是任意或所有类型。
  • 在函数内部使用泛型变量的时候,由于事先不知道它是哪种类型,所以不能随意的操作它的属性或方法
  • 如果参数传入的是数组,我们想打印他的长度:
function identity<T>(arg: T): T {
	console.log(arg.length);  // 报错: 类型“T”上不存在属性“length”
    return arg;
}
identity([1,2,3])

报错原因:
类型变量代表的是任意类型,所以使用这个函数可能传入的是`number`,而数字是没有 `.length`属性的。
  • 解决方法:
// 方法一:
function identity<T>(arg: T[]): T[] {
    console.log(arg.length);  // 不会报错
    return arg;
}
// 方法二:
function identity<T>(arg: Array<T>): Array<T> {
    console.log(arg.length);  // 不会报错
    return arg;
}

方法分析:
1. 泛型函数identity,接收类型参数T和参数arg
2. 它是个元素类型是T的数组,并返回元素类型是T的数组
3. 如果我们传入数字数组,将返回一个数字数组,因为此时 T的的类型为 number
4.  这可以让我们把泛型变量T当做类型的一部分使用,而不是整个类型,增加了灵活性

泛型类型

  • 泛型函数的类型与非泛型函数的类型没什么不同,只是有一个类型参数在最前面,像函数声明一样:
function identity<T>(arg: T): T {
    return arg;
}

let myIdentity: <T>(arg: T) => T = identity;
  • 我们也可以使用不同的泛型参数名,只要在数量上和使用方式上能对应上就可以。
function identity<T>(arg: T): T {
    return arg;
}

let myIdentity: <U>(arg: U) => U = identity;
  • 我们还可以使用带有调用签名的对象字面量来定义泛型函数:
function identity<T>(arg: T): T {
    return arg;
}

let myIdentity: {<T>(arg: T): T} = identity;
  • 还可以对象字面量拿出来做为一个接口:
interface GenericIdentityFn {
    <T>(arg: T): T;
}

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

let myIdentity: GenericIdentityFn = identity;
  • 把泛型参数当作整个接口的一个参数(比如: Dictionary<string>而不只是Dictionary
interface GenericIdentityFn<T> {
    (arg: T): T;
}

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

// 当我们使用 GenericIdentityFn的时候,传入一个类型参数来指定泛型类型(这里是:number)
let myIdentity: GenericIdentityFn<number> = identity;

泛型类 和 泛型接口

泛型类

  • 泛型类看上去与泛型接口差不多。 泛型类使用( <>)括起泛型类型,跟在类名后面。
class GenericNumber<T> {
  zeroValue: T | undefined
  add: ((x: T, y: T) => T) | undefined;
}

// 限制它只能使用number类型
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 1;
myGenericNumber.add = function(x, y) { return x + y; };
console.log(myGenericNumber.add(myGenericNumber.zeroValue,2)); // 3

// 限制它只能使用string类型
let stringNumeric = new GenericNumber<string>();
stringNumeric.zeroValue = "x";
stringNumeric.add = function(x, y) { return x + y; };

console.log(stringNumeric.add(stringNumeric.zeroValue, "y")); // xy

泛型接口

  • 使用接口的方式来定义一个函数需要符合的形状
interface SearchFunc {
  (source: string, subString: string): boolean;
}

let mySearch: SearchFunc;
mySearch = function(source: string, subString: string) {
    return source.search(subString) !== -1;
}
  • 使用含有泛型的接口来定义函数的形状:
interface CreateArrayFunc {
    <T>(length: number, value: T): Array<T>;
}

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

createArray(3, 'x'); // ['x', 'x', 'x']
  • 把泛型参数提前到接口名上:
interface CreateArrayFunc<T> {
    (length: number, value: T): Array<T>;
}

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

createArray(3, 'x'); // ['x', 'x', 'x']
  • 12
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一颗不甘坠落的流星

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值