大家好,这里是
皮哥饭店
,帮忙大家下饭,再不吃饱就要被赶回家啦。
泛型的定义和作用
泛型是 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
。
泛型束缚
泛型束缚用于限度传入的类型参数必须合乎某些条件,以便在函数或类中应用。
泛型束缚能够通过以下两种形式实现:
- 通过继承接口的形式实现泛型束缚
- 应用
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 x
将key
转换为 "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);
在这个例子中,咱们将 string
和 number
作为类型参数传递给 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);
在这个例子中,咱们将 string
和 number
作为类型参数传递给 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>
的数组类型,该数组中的元素是number
或string
类型。如果尝试这样做,则会呈现编译器谬误。 - 无法访问类型参数的属性或办法。因为类型参数可能是任何类型,编译器无奈推断类型参数具备哪些属性或办法。因而,在应用泛型时,只能拜访类型参数的公共成员。
- 不能应用运算符。因为泛型是在运行时确定的,因而无奈在泛型类型上应用运算符,例如
+
或-
。
如何进步泛型的应用
以下是一些能够进步泛型应用的技巧:
- 应用泛型束缚。泛型束缚能够限度类型参数的类型范畴,从而能够在类型参数上应用属性和办法。
- 应用默认类型参数。默认类型参数容许在调用泛型时省略类型参数,并应用默认值代替。
- 应用泛型类型推断。泛型类型推断能够让编译器主动推断类型参数的类型。
- 编写通用的代码。尽量编写可复用的通用代码,以便在须要时能够轻松地重复使用。