乐趣区

关于typescript:加更-泛型加深理解-下饭下饭

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

泛型的定义和作用

泛型是 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 类型。如果尝试这样做,则会呈现编译器谬误。
  • 无法访问类型参数的属性或办法。因为类型参数可能是任何类型,编译器无奈推断类型参数具备哪些属性或办法。因而,在应用泛型时,只能拜访类型参数的公共成员。
  • 不能应用运算符。因为泛型是在运行时确定的,因而无奈在泛型类型上应用运算符,例如 +-

如何进步泛型的应用

以下是一些能够进步泛型应用的技巧:

  • 应用泛型束缚。泛型束缚能够限度类型参数的类型范畴,从而能够在类型参数上应用属性和办法。
  • 应用默认类型参数。默认类型参数容许在调用泛型时省略类型参数,并应用默认值代替。
  • 应用泛型类型推断。泛型类型推断能够让编译器主动推断类型参数的类型。
  • 编写通用的代码。尽量编写可复用的通用代码,以便在须要时能够轻松地重复使用。
退出移动版