在 TypeScript 中,装饰器是一项非常重要的特性。它可以使我们在代码中优雅地使用一些常见的设计模式,同时也可以用于实现一些高级特性,例如依赖注入。
在本文中,我们将介绍 TypeScript 中装饰器的基本用法,并结合示例代码深入了解其实现原理和使用方法。
什么是装饰器?
在 TypeScript 中,装饰器是一种特殊的声明。它可以被附加到类声明、方法、属性或参数上,以修改它们的行为。
例如,我们可以使用 @Component
装饰器来标记一个类,表示该类是一个组件:
@Component class MyComponent { // ... }
此时,我们可以通过 MyComponent
类的实例来访问其属性和方法。
装饰器的基本用法
装饰器可以用于修饰类、方法和属性,其基本语法如下:
@decorator class MyClass { @decorator myMethod() {} @decorator myProperty: string; }
其中,@decorator
表示装饰器。它可以是一个函数,也可以是一个类。当作为函数时,装饰器函数接收一个参数:
- 对于类装饰器,该参数是被修饰的类本身;
- 对于属性装饰器,该参数是被修饰的类的原型对象;
- 对于方法装饰器,该参数是被修饰的类的原型对象和方法名;
- 对于参数装饰器,该参数是被修饰的方法的原型对象和参数所在的位置。
现在我们来看一些示例代码,深入了解装饰器的基本用法。
类装饰器
类装饰器可以用于修改类的行为和属性。比如,我们可以使用 @memoize
装饰器来将类的方法变为记忆函数:
-- -------------------- ---- ------- -------- --------------- ----- ----------- - ----- -------- - ----------------- ----- ----- - --- ------ ---------------- - ----------------- - ----- --- - --------------------- -- ---------------- - ------ --------------- - ---- - ----- ------ - -------------------- ------ -------------- -------- ------ ------- - -- ------ ----------- - ----- ------- - -------- ----------------------- -- - ----------------------- --------- ----------------- ------ - - -- - - ----- --- - --- ---------- --------------------------- --- -- ---------- --------- -------------- --------------------------- --- -- --- ------- ------- --
在上面的例子中,@memoize
装饰器接受一个函数,返回一个新的函数,在新函数中添加了记忆函数的实现。当调用 expensiveCalculation
函数时,如果有相同的参数,就会直接返回之前的结果,避免重复计算。
属性装饰器
属性装饰器可以用于修改类的属性。比如,我们可以使用 @log
装饰器来记录属性的读写操作:
-- -------------------- ---- ------- -------- ----------- ---- - --- ----- - ------------ ----- ------ - ---------- - -------------------- -------- ------ - ----------- ------ ------ -- ----- ------ - ------------------ - -------------------- -------- ------ - -------------- ----- - --------- -- ----------------------------- ---- - ---- ------- ---- ------- ----------- ----- ------------- ---- --- - ----- ------- - ---- ----------- ------ - -------- ------- - ----- --- - --- ---------- ---------------------------- -- ------- -------- ---------- - ------- ----- -------------- - ---- ------- -- ------- -------- ---------- - --- ----- ---------------------------- -- ------- -------- ---------- - --- -----
在上面的例子中,@log
装饰器接受一个类的原型对象和属性名,修改了该属性的 getter 和 setter 方法,以便记录属性的读写操作。
方法装饰器
方法装饰器可以用于修改类的方法。比如,我们可以使用 @throttle
装饰器来将方法变为节流函数:
-- -------------------- ---- ------- -------- -------------- ------- - ------ ---------------- ----- ----------- - ----- -------- - ----------------- --- ------ ---------------- - ----------------- - -- -------- - ----- - ------------- -- - ----- - ----- -- ------ ------ -------------------- ------ - -- ------ ----------- -- - ----- ------- - -------------- --------- - ------------------------ - - ----- --- - --- ---------- -------------- -- -------- ------------- -- -------------- ----- ------------- -- -------------- ----- ------------- -- -------------- ----- -- ------- ----- ------ --------
在上面的例子中,@throttle
装饰器接受一个数值参数 wait
,返回一个新的装饰器函数。该装饰器函数接受三个参数,然后修改方法的行为,使其成为一个节流函数。在示例代码中,只有最后一次点击操作产生了效果,并在 500ms 后输出了 Clicked!。
参数装饰器
参数装饰器可以用于修改方法的参数。比如,我们可以使用 @required
装饰器来检查方法参数是否为空:
-- -------------------- ---- ------- -------- ---------------- ----- ------ - ----- -------- - ------------- ------------ - ----------------- - -- ------------ -- ----- - ----- --- ---------------- ---------- -- ----------- - ------ -------------------- ------ -- ------ ------- - ----- ------- - -------------------- ------- ------- ------- ------- - ------ ------ - ------- - - ----- --- - --- ---------- ----------------------- --------- -- ------------ -------------------- --------- -- -------- ------ --------- --- -- --------
在上面的例子中,@required
装饰器接受三个参数,其中第三个参数 index
表示被修饰的参数在方法的参数列表中的位置。在新的方法中,我们首先检查该参数是否为空,如果为空就抛出错误。
装饰器的应用
除了上述基本用法之外,装饰器还可以用于更高级的应用,例如依赖注入、传递元数据、生成路由等功能。
依赖注入
依赖注入(Dependency Injection)是一种常见的设计模式,它通过将依赖关系从代码中移除,使得代码更加容易测试、维护和扩展。
我们可以使用 TypeScript 中的装饰器和反射技术来实现依赖注入。下面是一个简单的示例:
-- -------------------- ---- ------- ----- --------- - ------- -------- - --- ----------- ------- --------------- ------- -------- ---- - ------------------------ --------- - ----------------- -------- - - ----- ------- - ------------------------- -- -------- -- ----- - ----- --- -------------- --------------------- --- -------- - ----- ---------- - ---------------------------------------- -------- -- --- ----- ------ - --------------------- ---- -- -------------------- ------ --- ------------------- - - ----- --------- - --- ------------ ----- ----------- - ------- ----- - -- ----------- - ------ ------------- - - ---------------------------------- ------------- ----- ------------ - ------------------- ------------ ------------ -- -------- - --------------------- ----- ----------------------------------- - - ----- ----- - ----------------------- ------- ------------ ------------ ------------------------ ------- ------------- ------------- ----- - --------------------------- - - ----------------------------------- --------------
在上面的代码中,我们定义了一个 Container
类,用于管理不同的服务。我们将 UserService
和 OrderService
类注册到容器中,并将其中一个服务注入到 MyApp
类中。
注入的过程是通过 @Inject
装饰器实现的。该装饰器接受一个服务标识符,然后使用反射技术获得服务的类型,并通过 Container
类中的 resolve
方法获取该服务的实例。
在运行 MyApp
的 run
方法时,我们将使用容器中的 OrderService
实例,其中包含一个 userService
对象。每次调用 create
方法时,我们将使用 getNextId
方法生成新的订单 ID。由于该方法依赖于 UserService
,所以在依赖注入过程中,MyApp
中的 userService
属性会被自动注入为 UserService
的实例。
元数据和路由
装饰器还可以用于传递元数据和生成路由。在 TypeScript 中,我们可以使用 Reflect.metadata
函数传递元数据,使用 Express
库生成路由。
下面是一个示例代码,演示了如何将路由绑定到控制器中,并使用 @Route
装饰器将该方法绑定到路由上:
-- -------------------- ---- ------- ------ - -- ------- ---- ---------- ----- --- - ---------- -------- ----------------- ------- - ------ ---------------- - ----- ------ - ----------------- ----- ----- - ---------------------------------------- ---------------------------------------------- -- - ----- -- - ------------ ----- ----- - ---------------------------- ---- -- ------ -- ----- - ----------------- ----------- ----------- - --- ------------ -- ---- -------- -- - -------- ----------- ------- - ------ ---------------- ----- ----------- - ------------------------------- ----- ------------------ ------ ----------- -- - ------------------- ----- ------------ - ----------- ---------- ---------------- ---- ----------------- - ---------------- --------- - - ---------------- -- -- - ------------------- -- ------- -- ---- ------- ---
在上面的代码中,我们定义了一个 Controller
装饰器,其返回的函数将控制器类转换为一个新的 express.Router
对象,并将其中所有带有 @Route
装饰器的方法绑定到相应的路由上。
通过 @Route 装饰器,我们将 index
方法绑定到了 /api
路由的根路径上。最终,当我们运行该服务时,访问 http://localhost:3000/api
就会输出 Hello, world!。
总结
在 TypeScript 中,装饰器是非常重要的一项特性,它可以使我们在代码中优雅地使用一些常见的设计模式,同时还可以实现一些高级特性。在本文中,我们通过示例代码演示了装饰器的基本用法和一些应用场景,希望读者们能够更好地掌握 TypeScript 中的装饰器技术。
来源:JavaScript中文网 ,转载请注明来源 https://www.javascriptcn.com/post/64afbf1148841e9894be3c7b