对于下列函数,用于生成一个包含重复值的数组。
typescript
const getArray = (value: any, times: number = 5): any[] => {
return new Array(times).fill(value);
};
其中重复的次数由参数 times 指定,重复的值由参数 value 指定。
需要注意的是,由于使用了 any 类型,函数可以接受任何类型的值,但也失去了一些类型检查的好处
例如进行如下调用
ini
getArray([1], 2).forEach(item => {
console.log(item.length);
});
getArray([1], 2)
返回的是一个包含两个数组 [ [1], [1] ] 的数组。因此,在遍历时,item 的类型被推断为 any,并且访问 length 属性不会导致 TypeScript 报错。
然而对于以下调用
ini
getArray(2, 3).forEach(item => {
console.log(item.length);
});
getArray(2, 3) 返回的是一个包含三个值为 2 的元素的数组 [2, 2, 2]。同样,因为返回类型是 any[],在遍历时 item 的类型被推断为 any,尽管实际上这是一个包含数字的数组。因此,访问 length 属性也不会导致 TypeScript 报错。
此时,就展示了在使用 any 类型时,TypeScript 失去了对元素类型的静态类型检查,使得一些潜在的错误只能在运行时被发现。此时可以采用泛型解决这一问题。
typescript
const getArray = <T>(value: T, times: number = 5): T[] => {
return new Array(times).fill(value);
};
<T>
:这是泛型语法,表示定义一个泛型变量 T。泛型是一种参数化类型的方法,它允许你编写与类型无关的代码,提高代码的复用性。
(value: T, times: number = 5):T[]
:这个函数接受两个参数,value 和 times。value 参数的类型是 T,即泛型类型,而 times 参数的类型是数值,默认值为 5。函数的返回类型是一个数组,该数组中的元素类型是 T。
new Array(times).fill(value)
:这一行代码创建了一个长度为 times 的数组,并用 value 填充它。因为我们定义了 T,这里的数组类型就是 T[]。
整体上,这个函数的作用是生成一个包含重复值的数组,其中数组的类型由传入的 value 决定,重复次数由 times 决定。函数的返回类型是一个数组,其元素类型与传入的 value 类型相同。
随后再次进行调用
ini
getArray<number[]>([1], 2).forEach(item => {
console.log(item.length);
});
getArray<number>(2, 3).forEach(item => {
console.log(item.length); // 类型"number"上不存在属性"length"
});
第一次调用getArray([1], 2).forEach(item => {...})
;
这一行调用了 getArray 函数,传入的参数是数组 [1] 和数字 2。因为我们的 getArray 函数使用了泛型,TypeScript 推断出 T 是数组的类型。所以,第一次调用得到的结果是 [[1], [1]],这是一个包含两个数组的数组。
在 forEach 中,item 的类型是数组 [1],所以 item.length 是有效的,不会报错。
第二次调用getArray(2, 3).forEach(item => {...})
;
由于我们传入的是一个数字 2,这会导致在遍历时 item 的类型被推断为 number,而不是 number[]。因此,尝试访问 length 属性会导致 TypeScript 报错,因为数字类型并没有 length 属性。
在泛型的使用过程中,使用尖括号(<>)语法可以在调用泛型函数时明确指定泛型变量的具体类型。这就是所谓的类型参数传递。
ini
getArray<number[]>([1, 2], 3).forEach(item => {
console.log(item.length);
});
调用了 getArray<number[]>([1, 2], 3)
,通过 <number[]>
明确告诉 TypeScript,泛型变量 T
应该被替换为 number[]
。因此,在 getArray 函数中,所有使用 T 的地方都被替换为 number[]。这样,TypeScript 就知道我们期望得到一个包含 number[] 类型元素的数组。
在 TypeScript 中,如果省略了明确的类型参数,编译器会尝试根据传入函数的实际参数类型进行类型推断。这被称为类型推断。在这个例子中:
ini
getArray(2, 3).forEach(item => {
console.log(item.length); // 类型"number"上不存在属性"length"
});
没有显式地传递类型参数,TypeScript 会根据传入 getArray 函数的参数类型进行推断。在这里,你传入的是 2,因此 TypeScript 推断 T 为 number。在遍历时,item 的类型被认为是 number,而数字类型并没有 length 属性,因此会出现编译错误。
尽管 TypeScript 可以进行类型推断,但有时明确传递类型参数是一个好的实践,特别是在复杂的代码中,为了确保代码的清晰性和可读性。
泛型变量
在泛型函数中,使用泛型变量 表示一种通用的类型。当你在函数中使用这个泛型变量时,必须考虑它可能代表的任意类型。
r
const getLength = <T>(param: T): number => {
return param.length; // error 类型"T"上不存在属性"length"
};
param
是一个泛型变量,可以是任意类型。在这里,你尝试访问 param.length 属性,这对于数组和字符串是有效的,因为它们都有 length 属性。然而,对于其他类型(例如数值 number),它是无效的,因此 TypeScript 报错。
泛型是一种在编写通用、灵活且类型安全的代码时非常有用的工具。在编写泛型代码时,需要确保代码对任意类型都能正常工作,或者通过约束泛型变量的类型。例如,你可以使用 TypeScript 的类型约束来确保传入的参数有 length 属性:
typescript
const getLength = <T extends { length: number }>(param: T): number => {
return param.length;
};
这样,就告诉 TypeScript,T 必须是一个具有 length 属性的类型,从而避免了可能的错误。
额外举另一个例子,定义了一个具有两个泛型变量 T
和 U
的泛型函数 getArray。这个函数接受两个参数 param1 和 param2,它们的类型分别是 T 和 U。函数还接受一个 times 参数,表示生成数组的长度。
ini
const getArray = <T, U>(param1: T, param2: U, times: number): [T, U][] => {
return new Array(times).fill([param1, param2]);
};
函数返回一个二维数组,其中每个元素都是一个包含类型为 T
的第一个元素和类型为 U
的第二个元素的数组。
调用上述函数
ini
getArray(1, "a", 3).forEach(item => {
console.log(item[0].length); // error 类型"number"上不存在属性"length"
console.log(item[1].toFixed(2)); // error 属性"toFixed"在类型"string"上不存在
});
在调用 getArray(1, "a", 3)
时,生成的数组元素的类型为 [number, string]。然后,在 forEach 中遍历这个数组时,TypeScript 会根据数组元素的类型来推断每个元素的类型。
由于数组的第一个元素是数值类型(number),尝试访问 item[0].length 会导致错误,因为数值类型没有 length 属性。同样,尝试调用 item[1].toFixed(2) 会导致错误,因为字符串类型没有 toFixed 方法。
泛型函数类型
泛型函数类型是 TypeScript 中的一种特殊的类型,它允许你在函数类型中使用泛型参数。这使得函数的输入参数和返回值的类型可以动态地根据调用时传入的具体类型而变化。
函数签名
使用函数签名的方式:
javascript
const myGenericFunction: <T>(arg: T) => T = (arg) => {
return arg;
};
泛型函数类型定义:
r
<T>(arg: T) => T
定义了一个泛型函数类型。 表示泛型参数,其中 T 是一个占位符,代表类型参数。函数接受一个参数 arg,其类型为 T,并返回值的类型也是 T。这表示这个函数可以接受任意类型的参数,并返回相同的类型。
变量声明和赋值:
javascript
const myGenericFunction: <T>(arg: T) => T = (arg) => {
return arg;
};
在这个例子中,<T>
表示一个泛型参数,arg: T
表示函数参数的类型是泛型 T,而 => T
表示函数返回值的类型也是泛型 T。
具体函数实现:
lua
(arg) => {
return arg;
}
这个函数接受一个参数 arg,并返回相同的参数。由于我们在变量声明时已经指定了类型,这个函数的类型会自动匹配泛型函数类型的定义,确保参数和返回值的类型符合预期。
类型别名
另外一种比较直观的方式是采用类型别名
ini
type GenericFunction = <T>(arg: T) => T;
const myGenericFunction: GenericFunction = (arg) => {
return arg;
};
首先进行类型别名定义:
ini
type GenericFunction = <T>(arg: T) => T;
使用 type
关键字定义了一个类型别名 GenericFunction
。这个类型别名表示一个泛型函数类型,其中 表示一个泛型参数。这个泛型参数 T 是在函数调用时动态确定的,可以代表任意类型。
进行函数定义:
ini
const myGenericFunction: GenericFunction = (arg) => {
return arg;
};
使用刚刚定义泛型函数类型 GenericFunction
来声明一个具体的函数 myGenericFunction。这个函数接受一个参数 arg,类型是泛型 T,并返回同样的类型 T。在函数体内,它简单地返回接收到的参数 arg。
随后使用上述案例定义的泛型函数类型:
ini
const result = myGenericFunction("Hello, TypeScript!");
调用了 myGenericFunction,并传入了一个字符串作为参数。由于 GenericFunction 是泛型函数类型,TypeScript 根据传入的实际参数类型来确定泛型参数 T 的具体类型。在这个例子中,result 的类型会被推断为 string,因为我们传入的参数是一个字符串。
使用泛型函数类型的主要优势在于它提供了灵活性,允许你在需要时为函数的输入参数和返回值指定不同的类型,而不必在每个函数调用时都重复声明函数类型。
采用接口定义泛型函数
接口定义泛型函数类型
less
interface GetArray {
<T>(arg: T, times: number): T[];
}
使用接口 GetArray
定义了一个泛型函数类型。接口内有一个使用 定义的泛型函数,接受一个类型为 T 的参数 arg 和一个 number 类型的参数 times,并返回一个 T 类型的数组。
变量声明和赋值:
typescript
const getArray: GetArray = <T>(arg: T, times: number): T[] => {
return new Array(times).fill(arg);
};
声明了一个变量 getArray,并将其类型设置为上面定义的泛型函数类型 GetArray。在变量赋值的部分,我们提供了一个具体的函数实现,该函数的类型会与接口中定义的泛型函数类型相匹配。