【TS】泛型详解

泛型

基本用法

泛型可以用来创建可重用的组件,一个组件可以支持多种类型的数据。 这样用户就可以以自己的数据类型来使用组件

  • 创造一个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'

泛型变量

使用泛型创建像identity这样的泛型函数时,编译器要求你在函数体必须正确的使用这个通用的类型。
换句话说,你必须把这些参数当做是任意或所有类型。

  • 如果参数传入的是数组,我们像打印他的长度:
function identity<T>(arg: T): T {
	console.log(arg.length);  // 报错: 类型“T”上不存在属性“length”
    return arg;
}
identity([1,2,3])
  • 报错原因:类型变量代表的是任意类型,所以使用这个函数的人可能传入的是个数字,而数字是没有 .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;
}

let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 1;
myGenericNumber.add = function(x, y) { return x + y; };
console.log(myGenericNumber.add(myGenericNumber.zeroValue,2)); // 3
// 没有什么去限制它只能使用number类型。 也可以使用字符串或其它更复杂的类型。
let stringNumeric = new GenericNumber<string>();
stringNumeric.zeroValue = "x";
stringNumeric.add = function(x, y) { return x + y; };

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

泛型约束

未完待续。。。。。。。。。。。。。。。

上一篇:利用excel制作二维码


下一篇:内置函数zip()