当前位置: 欣欣网 > 码农

TypeScript进阶指南:泛型和装饰器的妙用

2024-02-16码农

TypeScript进阶指南:泛型和装饰器的妙用

TypeScript作为JavaScript的超集,为大型应用的开发提供了强大的工具和概念,使得代码更加健壮和可维护。在TypeScript的众多高级特性中,泛型和装饰器是两个极其强大的工具。它们能够提高代码的复用性,同时为复杂的问题提供清晰、类型安全的解决方案。在这篇进阶指南中,我将深入探讨泛型和装饰器的高级用法,并通过具体的代码示例来展现它们如何提升我们代码的质量。

泛型的妙用

泛型是TypeScript中实现代码复用的一个重要工具。它允许我们编写可适用于多种类型的组件,而不必牺牲类型的安全性。

基础概念

泛型可以被认为是类型的变量,它可以捕获传递给组件的类型信息,并在整个组件中使用这些信息。

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

在这个 identity 函数中, T 是一个类型变量,它捕获了用户提供的类型(即传递给 identity 的参数的类型)。这样,这个函数就可以返回几乎任何类型的输入,同时保持类型信息。

高级用法:约束泛型

我们不仅可以定义泛型,还可以对泛型施加约束,以限制泛型可以表示的类型。

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;}

在这个例子中,我们定义了一个 Lengthwise 接口,它有一个单独的 .length 属性。然后我们说,泛型 T 必须符合 Lengthwise 的形状,这意味着传递给 loggingIdentity 的参数必须有 .length 属性。

使用泛型的实际案例

假设我们正在编写一个前端应用程序,需要从服务器获取不同类型的资源。我们可以编写一个泛型函数,来处理对不同资源的GET请求。

async function getResource<T>(url: string): Promise<T> {
let response = await fetch(url);
let body = await response.json();
return body as T;
}
// 使用
interface User {
name: string;
email: string;
}
async function getUser(userId: string) {
const user = await getResource<User>(`/api/users/${userId}`);
console.log(user.name);}

在这个 getResource 函数中,我们使用了泛型 T 来捕获响应体的类型,这样我们就可以根据不同的调用安全地推断出返回类型。

装饰器的妙用

装饰器是TypeScript中用来修改类、方法、访问器、属性或参数的声明的特殊类型的声明。装饰器使用 @expression 这样的形式, expression 求值后必须为一个函数,它会在运行时被调用。

类装饰器

类装饰器在类声明之前被声明(紧靠着类声明)。类装饰器应用于类构造函数,可以用来监视、修改或替换类定义。

function sealed(constructor: Function) {
Object.seal(constructor);
Object.seal(constructor.prototype);
}
@sealed
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
return "Hello, " + this.greeting;
}}

在这个例子中, sealed 装饰器会封闭 Greeter 类,阻止添加新的属性,并且在实例化时标记为不可配置。

方法装饰器

方法装饰器声明在一个方法的声明之前(紧靠着方法声明)。它会被应用到方法的属性描述符上,可以用来监视、修改或替换方法定义。

function enumerable(value: boolean) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
descriptor.enumerable = value;
};
}
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
@enumerable(false)
greet() {
return "Hello, " + this.greeting;
}}

在这个例子中, enumerable 装饰器修改了 greet 方法的枚举性。当我们设置为 false 时,这个方法就不会出现在类的枚举属性中。

装饰器工厂

如果我们想自定义装饰器如何应用到一个声明上,我们可以写一个装饰器工厂函数。装饰器工厂是一个简单的函数,它返回表达我们装饰器在运行时调用方式的函数。

function format(formatString: string) {
return function (target: any, propertyKey: string) {
let value: string;
const getter = function () {
return formatString.replace("%s", value);
};
const setter = function (newVal: string) {
value = newVal;
};
Object.defineProperty(target, propertyKey, {
get: getter,
set: setter,
enumerable: true,
configurable: true
});
};
}
class Greeter {
@format("Hello, %s")
greeting: string;
constructor(message: string) {
this.greeting = message;
}}

在这个例子中, format 装饰器工厂允许我们将一个字符串模板应用于 Greeter 类的 greeting 属性。每次访问这个属性时,都会返回一个格式化后的字符串。

结论

泛型和装饰器是TypeScript提供的强大工具,它们能够极大地提高我们代码的复用性和可维护性。通过泛型,我们可以编写出适用于多种类型的通用组件,而装饰器则允许我们以声明性的方式修改和增强类和类成员的行为。掌握了这些高级特性,我们就可以编写出更加灵活和健壮的TypeScript代码。

如果喜欢我的内容,不妨点赞关注,我们下次再见!

大家注意:因为微信最近又改了推送机制,经常有小伙伴说错过了之前被删的文章,或者一些限时福利,错过了就是错过了。所以建议大家加个 星标 ,就能第一时间收到推送。

点个喜欢支持我吧,点个 在看 就更好了