泛型
泛型
泛型(Generics)是 TypeScript 提供的一个工具,它允许你在定义函数、接口或类的时候不具体指定它们将操作的数据的类型,而是在使用这些函数、接口或类的时候才确定具体的类型。这种方式提供了更大的灵活性,并且可以用于创建可重用的组件。
基础概念:想象你有一个函数,它可以返回数组中的第一个元素。在 JavaScript 中,不关心元素的具体类型,但在 TypeScript 中,我们优先考虑类型安全。
function getFirstElement(array: any[]): any { return array[0]; }
上面的函数可以返回任何类型的元素,但这样会失去 TypeScript 类型检查的好处。
使用泛型:泛型允许你创建一个工作于多种类型的组件,同时还能保持类型信息。
function getFirstElement<T>(array: T[]): T { return array[0]; } let numbers = [1, 2, 3]; let firstNumber = getFirstElement(numbers); // 类型为 number let strings = ["a", "b", "c"]; let firstString = getFirstElement(strings); // 类型为 string
在上面的例子中,
<T>
表示一个类型变量,你可以在调用getFirstElement
函数时指定它为任何类型。TypeScript 会根据传入的参数自动推断T
的类型。泛型约束:有时候你需要限制泛型的范围,确保它拥有特定的属性或方法。
interface Lengthwise { length: number; } function logLength<T extends Lengthwise>(arg: T): T { console.log(arg.length); return arg; } logLength({ length: 10 }); // 正常工作
在这个例子中,
<T extends Lengthwise>
表示任何T
必须符合Lengthwise
接口。这就是泛型约束。泛型接口和类:泛型也可以被用于接口和类,从而定义通用的结构。
interface GenericIdentityFn<T> { (arg: T): T; } function identity<T>(arg: T): T { return arg; } let myIdentity: GenericIdentityFn<number> = identity; // 类的泛型 class GenericNumber<T> { zeroValue: T; add: (x: T, y: T) => T; } let myGenericNumber = new GenericNumber<number>(); myGenericNumber.zeroValue = 0; myGenericNumber.add = function (x, y) { return x + y; };
这里的
GenericIdentityFn<T>
是一个泛型接口,而GenericNumber<T>
是一个泛型类。通过这种方式,你可以为不同的类型实例化类或者接口。
总的来说,泛型是 TypeScript 强大的功能之一,它增加了代码的灵活性和复用性,同时仍然保持严格的类型安全。通过学习和使用泛型,你可以编写出既通用又类型安全的代码。
泛型之 Hello World
泛型之 Hello World
- 泛型是 TypeScript 提供的一种工具,它允许我们在定义函数、接口或类的时候不预先指定具体的类型,而是在使用的时候再指定类型的一种特性。
- 使用泛型可以创建可重用的组件,一个组件可以支持多种类型的数据。这样做可以保持代码的灵活性和复用性。
例如:
function identity<T>(arg: T): T {
return arg;
}
let output1 = identity<string>("myString");
let output2 = identity<number>(100);
这里
identity
函数通过一个类型变量T
来捕获用户传入的类型(比如string
或number
),之后再用T
作为返回类型。当使用identity
函数时,你也同时传入了类型参数,告诉它T
即所需的类型。在上述例子中,我们明确地指定了
T
应该是string
类型(identity<string>
),以及number
类型(identity<number>
),因此对于output1
和output2
变量,TypeScript 知道它们分别是字符串和数字类型。当然,TypeScript 编译器也可以自动地推论出类型参数(即类型自动推断),所以你完全不必明确地传递类型参数
<string>
或<number>
:
let output1 = identity("myString"); // 类型推断为 string
let output2 = identity(100); // 类型推断为 number
- 在这个简化的例子中,TypeScript 根据传给
identity
函数的值自动地确定了T
的类型。这就如同 Hello World 级别的泛型使用,是泛型最基本的一个示例。
使用泛型变量
泛型是 TypeScript 提供的一种工具,它允许我们编写可重用代码的组件,同时保持类型的兼容性和安全。使用泛型变量时,我们可以创建能够处理任意类型的组件,而不是限制它们仅处理某一特定类型。这样做的好处是增加了代码的灵活性和可复用性,同时还能保持严格的类型检查。
基本概念:想象你有一个函数,该函数接受一个数组并返回该数组的第一个元素。如果没有泛型,你可能需要为每种可能的数组元素类型编写一个函数。但有了泛型,你可以写一个通用的函数,它可以工作于多种类型。
function getFirstElement<T>(array: T[]): T { return array[0]; }
在上面的例子中,
<T>
表示泛型类型。当你调用这个函数时,你可以指定T
代表的类型,或者让 TypeScript 自动根据传入的参数推断类型。在类中使用:泛型也可以在类定义中使用,使得类可以操作不特定的数据类型。
class DataHolder<T> { data: T; constructor(data: T) { this.data = data; } getData(): T { return this.data; } } let numberHolder = new DataHolder<number>(123); let stringHolder = new DataHolder<string>("hello");
这里
DataHolder
类通过使用泛型<T>
,成为了可以存储任何类型数据的通用容器。泛型约束:在某些情况下,你可能希望限制泛型所能代表的类型范围。通过使用泛型约束,你可以指定泛型继承自某个特定类或者拥有某些特定属性。
interface Lengthwise { length: number; } function loggingIdentity<T extends Lengthwise>(arg: T): T { console.log(arg.length); // Now we know it has a .length property, so no more error return arg; }
在上面的例子中,
T extends Lengthwise
表示T
必须符合Lengthwise
接口,即必须包含length
属性。这样在函数内部就可以安全地访问arg.length
了。
使用泛型允许你编写灵活且可重用的代码库,同时保持类型安全。理解并熟练运用泛型将大大提升你使用 TypeScript 的效率和代码质量。
泛型类型
泛型类型是 TypeScript 中的一个高级特性,它允许你定义一个类型或方法,这个类型或方法可以适用于多种数据类型。使用泛型可以创建可重用的组件,这些组件可以支持多种类型的数据。
泛型函数类型:
- 泛型也可以用在函数类型的定义上。比如说,我们有一个泛型接口定义一个函数:
interface GenericIdentityFn<T> { (arg: T): T; }
- 这个接口定义了一个函数,这个函数接收一个参数
arg
,这个参数和函数返回值都是同一类型T
。
- 泛型也可以用在函数类型的定义上。比如说,我们有一个泛型接口定义一个函数:
使用泛型类型:
当我们要实现这个接口时,我们会定义一个具体的函数,并且指定
T
的具体类型:function identity<T>(arg: T): T { return arg; } let myIdentity: GenericIdentityFn<number> = identity;
这里我们定义了
myIdentity
函数,它的类型是GenericIdentityFn<number>
,意味着实参和返回值类型都应该是number
类型。
泛型类类型:
泛型不仅可以用于定义接口或者函数,还可以用来定义类:
class GenericNumber<T> { zeroValue: T; add: (x: T, y: T) => T; } let myGenericNumber = new GenericNumber<number>(); myGenericNumber.zeroValue = 0; myGenericNumber.add = function (x, y) { return x + y; };
这个类
GenericNumber<T>
有一个成员zeroValue
和一个方法add
,它们的类型都依赖于T
。当我们创建一个新的GenericNumber<number>
的实例时,zeroValue
和add
的类型都被确定为number
。
泛型类型使得你能够编写灵活、可复用的代码库,你只需要定义一次方法或组件,然后就可以用不同的类型去使用它,而不需要每一种类型都重写方法或组件。这样既增强了代码的可维护性,也增加了程序的灵活性。
泛型类
泛型类是一种组件或类,它可以适用于多种数据类型的同时保持一定的类型安全。在 TypeScript 中,泛型类的定义与普通类非常相似,但它们会带有一个或多个类型变量。
泛型类的定义:使用尖括号
<>
来指定泛型参数。例如:class GenericNumber<T> { zeroValue: T; add: (x: T, y: T) => T; }
在这个例子中,
GenericNumber
类有一个名为T
的泛型类型参数。实例化泛型类:创建类的实例时,需要指定泛型类型。例如:
let myGenericNumber = new GenericNumber<number>(); myGenericNumber.zeroValue = 0; myGenericNumber.add = function (x, y) { return x + y; };
这里创建了一个
GenericNumber
的实例,用number
作为其类型参数,因此zeroValue
属性和add
方法都将处理数字类型的值。泛型类与不同类型一起使用:可以使用不同的类型参数来实例化泛型类,使得类可以以类型安全的方式工作于多种数据类型。例如:
let myGenericString = new GenericNumber<string>(); myGenericString.zeroValue = ""; myGenericString.add = function (x, y) { return x + y; };
在这个例子中,
GenericNumber
被实例化为接受字符串类型的参数,从而zeroValue
和add
现在适用于字符串操作。类的静态成员不能使用类的泛型类型参数:如果类具有静态成员,则这些成员不能访问类的泛型类型参数。每个实例化的类型对于静态成员来说是不可见的。
使用泛型类可以编写出更加灵活且可重用的代码组件,因为它们允许你在保持类型安全的前提下传入不同的类型。
泛型约束
泛型约束(Generic Constraints)是 TypeScript 中用来确保泛型类型符合特定结构或者包含必需的属性的一种机制。在使用泛型时,你可能想要限制泛型能够接受哪些类型的数据。这就是通过泛型约束实现的。
基本概念:
- 泛型给予我们创建可复用组件的能力,这些组件可以支持多种类型而不丢失其类型信息。
- 在有些情况下,你可能需要对这些类型进行一些限制,以保证它们具有某些共同的功能或属性。
例子:
- 假设你有一个函数,你希望这个函数能够处理任何带有
.length
属性的类型(如数组,字符串等)。 - 不使用泛型约束,这个函数可能会接收任何类型,但如果传入的类型没有
.length
属性,就会出错。 - 使用泛型约束,你可以明确指出该函数只接受有
.length
属性的类型。
- 假设你有一个函数,你希望这个函数能够处理任何带有
// 没有泛型约束的函数
function logLength<T>(arg: T): void {
// 这里可能会因为arg没有length属性而导致编译错误
console.log(arg.length);
}
// 使用泛型约束
interface Lengthwise {
length: number;
}
// 现在T被约束了,它必须符合Lengthwise接口
function logLengthWithConstraint<T extends Lengthwise>(arg: T): void {
// 因为arg一定有length属性,所以下面的代码是安全的
console.log(arg.length);
}
logLengthWithConstraint([1, 2, 3]); // 输出:3
logLengthWithConstraint("hello"); // 输出:5
// 下面将导致编译错误,因为数字没有length属性
// logLengthWithConstraint(10);
- 泛型约束的语法:
- 你通过
extends
关键字添加泛型约束,后面跟着约束条件。 - 约束条件通常是一个接口或类型别名,它定义了泛型应该满足的属性或方法。
- 你通过
// T extends U的语法表示T必须符合U的形状
function identity<T extends U, U>(arg: T): U {
return arg;
}
- 使用泛型约束时的注意事项:
- 过度使用泛型约束可能会导致你的代码变得过于复杂。
- 尽量让泛型保持简单,只在必要时添加约束。
- 当你无法提前知道具体的类型,但是需要确保类型间有一定的关系或兼容性时,泛型约束非常有用。
在泛型约束中使用类型参数
泛型约束中使用类型参数的概念指的是在 TypeScript 中,当你定义一个泛型函数时,可能想限制这个函数可以接受的类型。比如说,你想确保这个类型有某个特定的属性或方法。这时候,你就可以使用泛型约束。
基本的泛型约束通常是用一个接口或者 type 来定义那些必须存在于传入类型的属性或方法。
interface Lengthwise { length: number; } function logLength<T extends Lengthwise>(arg: T): T { console.log(arg.length); return arg; }
在这个例子中,
T extends Lengthwise
表示任何传入logLength
函数的类型 T 都必须满足Lengthwise
接口,即具有length
属性。泛型约束中还可以使用类型参数来定义另一个类型参数的约束。
function getProperty<T, K extends keyof T>(obj: T, key: K) { return obj[key]; } let x = { a: 1, b: 2, c: 3, d: 4 }; getProperty(x, "a"); // 正常运行 // getProperty(x, "m"); // 错误:Argument of type '"m"' is not assignable to parameter of type '"a" | "b" | "c" | "d"'.
在这里,
K extends keyof T
意味着 K 必须是 T 的键集合(即 T 的所有键的类型)中的一个。这样就能确保getProperty
函数不会尝试访问对象上不存在的属性,提供了类型安全。
通过使用泛型约束,可以编写出更加安全、适应性强的代码,因为你可以预先定义并保证某些类型的条件和结构。
在泛型里使用类类型
在 TypeScript 的泛型约束中,使用类类型允许你指定一个泛型不仅要符合某个接口,而且还必须是某个特定类或其子类的实例。这样就可以确保泛型不只满足结构上的约束,同时也可以享有类本身提供的方法和属性。
- 泛型约束基础:通常我们使用泛型来创建可重用的组件,但是有时候我们需要对泛型进行一些限制,以确保它们具有我们需要的特定功能。这时,我们会使用
extends
关键字来约束泛型。
interface Lengthwise {
length: number;
}
function logLength<T extends Lengthwise>(item: T): void {
console.log(item.length);
}
在上述代码中,T extends Lengthwise
意味着任何使用logLength
函数的泛型T
必须具有length
属性。
- 在泛型中使用类类型:通过在泛型约束中使用类类型,你可以确保泛型参数是一个特定的类,或者是继承自该类的子类。这使你能够创建出既具有类的特性又具备泛型灵活性的函数或类。
class Animal {
numLegs: number;
constructor(numLegs: number) {
this.numLegs = numLegs;
}
walk() {
console.log(`Walking on ${this.numLegs} legs.`);
}
}
class Dog extends Animal {
bark() {
console.log("Woof!");
}
}
function createInstance<A extends Animal>(C: new () => A): A {
return new C();
}
在上面的代码中,createInstance
函数要求传入的C
参数必须是一个类,并且这个类必须有一个无参数的构造函数,并且返回值的类型为A
,其中A
必须是Animal
的实例或者派生自Animal
类的类的实例。
- 使用
createInstance
:
const myDog = createInstance(Dog);
myDog.walk(); // 正常工作,因为Dog继承自Animal,拥有walk方法
myDog.bark(); // 正常工作,Dog类有bark方法
在上述例子中,myDog
是通过createInstance
函数创建的Dog
实例,因为Dog
类符合createInstance
函数中对于泛型参数A
的约束(即A
是Animal
的实例或其子类)。
这种技术允许你编写出既通用又安全的代码,因为你可以利用泛型来写出适用于多种类型的代码,同时又能保证这些类型具有你所需的特定属性或方法。