加更 泛型加深理解 下饭下饭

大家好,这里是皮哥饭店,帮助大家下饭,再不吃饱就要被赶回家啦。

泛型的定义和作用

泛型是 TypeScript 中的一种高级类型,它允许你在定义函数、类、接口时不指定具体类型,而是将类型作为参数传入,从而实现通用性更强的代码。

在学习泛型的时候,你需要了解以下概念:

  • 泛型类型参数(type parameter):用于指定泛型类型的占位符。
  • 泛型函数(generic function):使用泛型类型参数的函数。
  • 泛型类(generic class):使用泛型类型参数的类。
  • 泛型约束(generic constraint):用于对泛型类型参数进行约束,以保证泛型类型参数的特定属性或方法存在。

下面是一个简单的泛型函数示例:

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

在这个函数中,<T> 表示这是一个泛型函数,并且 T 是一个泛型类型参数。这个函数接收一个参数 arg,类型为 T,并将其原封不动地返回。

泛型函数的调用方式与普通函数类似,不同之处在于调用时需要指定具体类型参数:

const result1 = identity<string>("hello");
const result2 = identity<number>(123);

在这个示例中,我们分别调用了 identity 函数,并指定了具体的类型参数。第一个调用中,类型参数为 string,返回值类型也为 string;第二个调用中,类型参数为 number,返回值类型也为 number

通过这个例子,你可以初步了解泛型的基本用法,包括泛型函数的定义和调用。接下来,你需要更深入地了解泛型的使用和约束。

泛型函数

使用泛型函数

使用泛型函数时,需要在函数名后面用尖括号(<>)指定类型参数。例如:

function identity<T>(arg: T): T {
 return arg;
}
let output = identity<string>("hello world");

在这个例子中,identity是一个泛型函数,类型参数为<T>,传入的参数和返回值都是类型T。在调用identity函数时,将类型参数指定为string,函数返回值为"hello world"

泛型接口

定义泛型接口

泛型接口是一种可以指定参数类型的接口。定义泛型接口的语法如下:

interface interfaceName<T> {
 propertyName: T;
}

这里的<T>表示一个类型参数,可以根据需要自定义类型参数的名称。泛型函数可以包含多个类型参数,每个参数之间用逗号分隔。

使用泛型接口

使用泛型接口时,需要在接口名后面用尖括号(<>)指定类型参数。例如:

interface GenericIdentityFn<T> {
 (arg: T): T;
}
function identity<T>(arg: T): T {
 return arg;
}
let myIdentity: GenericIdentityFn<number> = identity;

在这个例子中,GenericIdentityFn是一个泛型接口,类型参数为<T>,接口中定义了一个接受类型为T的参数并返回类型为T的函数。在调用myIdentity函数时,将类型参数指定为number,函数返回值为一个number类型的值。

泛型类

定义泛型类

泛型类是具有一个或多个类型参数的类。定义泛型类的语法如下:

class ClassName<T> {
 propertyName: T;
}

这里的<T>表示一个类型参数,可以根据需要自定义类型参数的名称。

使用泛型类

使用泛型类与使用泛型函数和泛型接口的方式类似。首先,我们需要在实例化泛型类时指定类型参数。

例如,如果我们有一个泛型类MyClass<T>,我们可以这样使用它:

// 定义泛型类
class MyClass<T> {
 value: T;
 constructor(value: T) {
 this.value = value;
 }
}
// 实例化泛型类
const myInstance = new MyClass<string>("Hello, world!");
// 访问泛型类的属性
console.log(myInstance.value); // 输出:Hello, world!

在这个例子中,我们定义了一个泛型类MyClass,并使用T作为类型参数。类有一个属性value,它的类型为T。我们可以在构造函数中使用value初始化该属性。

在实例化泛型类时,我们必须为T指定一个类型参数。在本例中,我们实例化了一个MyClass的实例,并传入了一个字符串类型的参数"Hello, world!"

最后,我们通过访问实例的value属性,输出了"Hello, world!"

如果我们要使用一个非常通用的类型参数,比如any,那么我们可以省略类型参数的指定,让 TypeScript 推断类型。例如

// 实例化泛型类并省略类型参数
const myInstance = new MyClass("Hello, world!");
// 访问泛型类的属性
console.log(myInstance.value); // 输出:Hello, world!

在这个例子中,我们实例化了一个MyClass的实例,并传入了一个字符串类型的参数"Hello, world!"。由于我们没有指定类型参数,TypeScript 推断出T的类型为string

泛型约束

泛型约束用于限制传入的类型参数必须符合某些条件,以便在函数或类中使用。

泛型约束可以通过以下两种方式实现:

  1. 通过继承接口的方式实现泛型约束
  2. 使用keyof关键字对泛型类型参数进行约束

下面分别介绍这两种方式。

通过继承接口的方式实现泛型约束

通过继承接口的方式实现泛型约束,可以保证传入的类型参数符合接口定义的要求。

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;
}
loggingIdentity("hello"); // Output: 5

在上面的例子中,loggingIdentity函数接受一个泛型参数T,并对T进行约束,使其必须符合Lengthwise接口定义的要求,即具有length属性。

通过这种方式,我们可以在函数中直接使用传入参数的length属性,而不需要进行类型判断。

使用keyof关键字对泛型类型参数进行约束

使用keyof关键字对泛型类型参数进行约束,可以限制传入的类型参数必须是指定类型的属性之一。

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"); // OK
getProperty(x, "e"); // Error

在上面的例子中,getProperty函数接受两个参数,一个是对象obj,一个是属性名key。通过使用keyof T,可以限制传入的类型参数K必须是对象T的属性之一。这样可以保证obj[key]的类型是有效的。

需要注意的是,在使用keyof关键字时,属性名必须是一个已知的类型。因此,如果要使用一个变量作为属性名,可以使用索引类型查询操作符[]

function getProperty<T, K extends keyof T>(obj: T, key: K) {
 return obj[key];
}
let x = { a: 1, b: 2, c: 3, d: 4 };
let key = "a";
getProperty(x, key); // Error
getProperty(x, key as keyof typeof x); // OK

在上面的例子中,变量key的类型是string,无法直接作为getProperty函数的第二个参数传入。可以使用keyof typeof xkey转换为"a" | "b" | "c" | "d"类型,即对象x的属性之一。

能看到这里,恭喜你大兄弟 你已经超越了70%的人

泛型默认值

在 TypeScript 中,我们可以为泛型类型参数指定默认值,当在使用泛型时未显式传入该参数的值时,将使用指定的默认值。语法如下:

function functionName<T = defaultValue>(args: T): void {
 // 函数体
}

其中 <T = defaultValue> 表示泛型类型参数 T 有一个默认值 defaultValue。

举个例子,我们定义一个返回数组最后一个元素的函数,如果数组为空,则返回默认值 -1

function last<T = number>(arr: T[]): T | number {
 return arr.length > 0 ? arr[arr.length - 1] : -1;
}
console.log(last([1, 2, 3])); // 输出 3
console.log(last([])); // 输出 -1
console.log(last<string>([])); // 输出 -1

在上面的代码中,我们使用泛型类型参数 T 来表示数组的元素类型,如果没有传递泛型类型参数,则默认为 number。在函数实现中,我们使用了默认值 -1,当数组为空时返回该默认值。

注意,在这里我们传递了一个空数组,由于我们没有传递泛型类型参数,因此默认为 number 类型。所以即使我们传递的是一个空的 string 数组,最终的返回值依然是 -1

当然,如果我们显式指定泛型类型参数,比如传递了一个 string 类型的数组,那么最终的返回值将还是 -1,因为我们使用了默认的泛型类型参数 number

泛型默认值的定义

泛型默认值指的是当泛型没有明确传入具体类型时,可以为泛型设置默认值,这样就可以使用默认值代替具体类型。

在定义泛型时,使用 = 号为泛型设置默认值,如下所示:

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

上述代码中,泛型 T 设置默认值为 string,当没有明确传入具体类型时,T 会默认为 string 类型。

泛型默认值的使用

使用泛型默认值时,可以在不传入具体类型的情况下使用泛型。

function identity<T = string>(arg: T): T {
 return arg;
}
const result1 = identity("hello"); // result1 类型为 string
const result2 = identity(123); // result2 类型为 number
const result3 = identity(); // result3 类型为 string,因为 T 默认为 string 类型

上述代码中,调用 identity 函数时,第一个参数传入字符串,第二个参数传入数字,第三个参数不传入具体类型。因为泛型 T 设置了默认值为 string,所以第三个参数的类型为 string

能看到这里,恭喜你大兄弟 你已经超越了90%的人

泛型类型推断

类型推断的概念

类型推断指的是 TypeScript 在没有明确指定类型的情况下,根据变量的使用上下文自动推断变量的类型。这个过程是在编译期间进行的。

泛型类型推断的规则

在函数调用时,如果没有明确指定泛型类型参数,TypeScript 会根据传入的参数自动推断泛型类型参数的类型。

function identity<T>(arg: T): T {
 return arg;
}
const result = identity("hello"); // result 类型为 string

上述代码中,调用 identity 函数时,传入参数 "hello",TypeScript 会自动推断出 T 类型为 string

此外,TypeScript 还支持从函数返回值推断泛型类型参数的类型。

function createArray<T>(length: number, value: T): T[] {
 return Array.from({ length }, () => value);
}
const result = createArray(3, "hello"); // result 类型为 string[]

上述代码中,调用 createArray 函数时,传入参数 3"hello",TypeScript 会自动推断出 T 类型为 string,从而使 result 的类型为 string[]

泛型在函数中的应用

在 TypeScript 中,我们可以使用泛型来创建可重用的函数。通过将类型参数传递给函数,我们可以确保该函数适用于多种不同的类型,而不是只适用于一种类型。

泛型函数的定义如下:

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

在这个例子中,<T> 表示类型参数。这意味着 identity 函数接受一个类型为 T 的参数,并返回一个类型为 T 的值。

调用泛型函数时,我们可以传递不同的类型参数:

let output1 = identity<string>("hello");
let output2 = identity<number>(42);

在这个例子中,我们将 stringnumber 作为类型参数传递给 identity 函数,并且它们都工作得很好。

泛型在类中的应用

与函数一样,我们可以在 TypeScript 中使用泛型来创建可重用的类。通过将类型参数传递给类,我们可以确保该类适用于多种不同的类型,而不是只适用于一种类型。

泛型类的定义如下:

class GenericClass<T> {
 private _value: T;
 constructor(value: T) {
 this._value = value;
 }
 getValue(): T {
 return this._value;
 }
}

在这个例子中,<T> 表示类型参数。这意味着 GenericClass 类接受一个类型为 T 的参数,并且具有一个名为 _value 的私有属性和一个名为 getValue 的公共方法,它返回一个类型为 T 的值。

调用泛型类时,我们可以传递不同的类型参数:

let stringClass = new GenericClass<string>("hello");
let numberClass = new GenericClass<number>(42);

在这个例子中,我们将 stringnumber 作为类型参数传递给 GenericClass 类,并且它们都工作得很好。

泛型在接口中的应用

与函数和类一样,我们可以在 TypeScript 中使用泛型来创建可重用的接口。通过将类型参数传递给接口,我们可以确保该接口适用于多种不同的类型,而不是只适用于一种类型。

泛型接口的定义如下:

interface GenericInterface<T> {
 value: T;
 getValue(): T;
}

在这个例子中,<T> 表示类型参数。这意味着 GenericInterface 接口有一个名为 value 的类型为 T 的属性和一个名为 getValue 的方法,它返回一个类型为 T 的值。

调用泛型接口时,我们可以传递不同的类型参数:

let stringInterface: GenericInterface<string> = {
 value: "hello",
 getValue() {
 return this.value;
 },
};
let numberInterface
能看到这里,恭喜你大兄弟 你已经超越了98%的人

泛型的局限性

尽管泛型非常灵活,但是仍然存在一些局限性。以下是一些常见的泛型局限性:

  • 不能使用原始类型作为类型参数。例如,无法声明一个 Array<number> 的数组类型,该数组中的元素是 numberstring 类型。如果尝试这样做,则会出现编译器错误。
  • 无法访问类型参数的属性或方法。由于类型参数可能是任何类型,编译器无法推断类型参数具有哪些属性或方法。因此,在使用泛型时,只能访问类型参数的公共成员。
  • 不能使用运算符。由于泛型是在运行时确定的,因此无法在泛型类型上使用运算符,例如 +-

如何提高泛型的使用

以下是一些可以提高泛型使用的技巧:

  • 使用泛型约束。泛型约束可以限制类型参数的类型范围,从而可以在类型参数上使用属性和方法。
  • 使用默认类型参数。默认类型参数允许在调用泛型时省略类型参数,并使用默认值代替。
  • 使用泛型类型推断。泛型类型推断可以让编译器自动推断类型参数的类型。
  • 编写通用的代码。尽量编写可复用的通用代码,以便在需要时可以轻松地重复使用。
作者:皮哥饭店原文地址:https://segmentfault.com/a/1190000043585464

%s 个评论

要回复文章请先登录注册