在现代软件开发中,我们不断面临着为代码添加新功能而不改变其核心逻辑的需求。TypeScript 装饰器提供了一种优雅的解决方案,使我们能够以声明式的方式扩展类和方法的行为,而无需修改其原始代码。通过装饰器,我们能够改善代码的可读性和可维护性,实现诸如功能性增强、代码组织、错误处理等目标。值得注意的是,一些流行框架如 Angular、NestJS、TypeORM 等都是基于装饰器的设计原理,它们广泛地利用装饰器来简化开发过程,并提高代码的质量和可维护性。同时,控制反转(Inversion of Control)和容器(Dependency Injection Container)等设计模式也与装饰器息息相关,它们通过装饰器来实现依赖注入,提供了一种灵活的组件管理方式。本文将介绍 TypeScript 装饰器的相关用法以及实际应用场景。
1.准备工作
Decorator 装饰器是ES7 的一个新语法,目前为实验性特性,在未来的版本中可能会发生改变。
因此,想要在 TypeScript 中使用装饰器,首先要先开启这项实验性特性。具体操作为:
1.使用tsc --init
生成初始化 ts 配置文件(确保已安装typescript为全局模块)
2.打开tsconfig.json文件开启装饰器选项:全局搜索 experimentalDecorators 设置为 true
2.装饰器分类
装饰器是一种特殊类型的声明,它能够被附加到类,方法,访问器,属性或参数上,被添加到不同地方的装饰器有不同的名称和特点。
下面将依次介绍以上分类的简单用法以及使用场景。
3.类装饰器
(1)定义一个装饰器
const CreatePerson: ClassDecorator = (target: Function) => { target.prototype.name='luoaoxuan' target.prototype.getAge=()=>{ return 18 } }
(2)定义类并使用装饰器
@CreatePerson class Person { constructor() { } }
类装饰器函数会把class Person的构造函数传入CreatePerson函数作为第一个参数。在紧贴类定义之前使用 @函数名 方式调用装饰器。
(3)测试
const person:any = new Person(); console.log(person.name,person.getAge()) // 输出 luoaoxuan 18
使用类装饰器,可以将类中方法与属性的定义交由装饰器来完成,而无需在类中进行定义,这便是 控制反转 的实现原理。
(4)装饰器工厂
使用一个外层函数返回一个装饰器函数的形式,由外层函数接收参数,提供给内层真正的的装饰器使用。
const CreatePerson=(name:string,age:number):ClassDecorator =>{ // 接收参数 return (target: Function) => { // 返回真正的装饰器函数 target.prototype.name=name target.prototype.getAge=()=>{ return age } } } @CreatePerson('luoaoxuan',18) // 传递参数 class Person { constructor() { } } const person:any = new Person(); console.log(person.name,person.getAge()) // 输出 luoaoxuan 18
(5)装饰器组合
可以使用多个装饰器作用于同一个对象上:
const CreatePersonName=(name:string):ClassDecorator =>{ return (target: Function) => { target.prototype.name=name } } const CreatePersonAge=(age:number):ClassDecorator =>{ return (target: Function) => { target.prototype.getAge=()=>{ return age } } } @CreatePersonName('luoaoxuan') @CreatePersonAge(18) class Person { constructor() { } } const person:any = new Person(); console.log(person.name,person.getAge()) // 输出 luoaoxuan 18
4.方法装饰器
方法装饰器接受三个参数:
对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
成员的名字。
成员的属性描述符。
const GetSum = (nums: number[]): MethodDecorator => { // 外层接收参数数组 return (target, propertyKey, descriptor: PropertyDescriptor) => { // 内层返回真正的装饰器 // console.log(target, propertyKey, descriptor); let fn = descriptor.value // 方法 // 调用方法 fn(nums.reduce((previousValue, currentValue) => previousValue + currentValue, 0)) // reduce求和 } } class Utils { constructor() { } @GetSum([1, 2, 3, 4, 5, 6]) getSum(result: number) { console.log(result); // 输出 21 } }
这里实现一个数组求和的方法装饰器,我们同样将数组求和的方法定义交由装饰器来完成,在方法定义之前使用 @函数名 方式调用装饰器。
5.属性装饰器
属性装饰器接受两个参数:
对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
属性的名字。
const SetName = (name: string): PropertyDecorator => { return (target: any, propertyKey) => { // console.log(target, propertyKey); target[propertyKey] = name // 初始化 name 操作 } } class Person { @SetName('luoaoxuan') name?: string; constructor() { } } const person = new Person(); console.log(person.name); // 输出 luoaoxuan
这里实现一个数据初始化的属性装饰器,将数据的初始化交由装饰器来完成,在属性定义之前使用 @函数名 方式调用装饰器。
6.参数装饰器
(1)参数装饰器的简单实现
对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
成员的名字。
参数在函数参数列表中的索引。
const Test = (): ParameterDecorator => { return (target, proptyName, index) => { console.log(target, proptyName, index); // 输出 {} create 0 } } class A { constructor() { } create(@Test() name: string) { } }
(2)参数装饰器与方法装饰器的配合使用
这里有如下要求:按照传入的key值从用户信息中解构得到相应的value值,如果没有传入key,则直接返回整个用户信息。需要使用方法装饰器与参数装饰器配合使用。
let tempKey: string // 存储临时 key const GetValue = (info: any): MethodDecorator => { return (target, propertyKey, descriptor: PropertyDescriptor) => { let fn = descriptor.value // 方法 fn(tempKey ? info[tempKey] : info) // 有key则返回对应value 无key则返回整个对象 } } const Value = (key?: string): ParameterDecorator => { return (target, PropertyKey, parameterIndex) => { tempKey = key as string } } class Person { constructor() { } @GetValue({ name: 'luoaoxuan', age: 18 ,id:'10000'}) getNameValue(@Value('name') name?: string) { console.log(name) // 输出 luoaoxuan } @GetValue({ name: 'luoaoxuan', age: 18 ,id:'10000'}) getAllValue(@Value() obj?: Object) { console.log(obj) // 输出 { name: 'luoaoxuan', age: 18, id: '10000' } } }
参数装饰器常用于对象的解构,数据的过滤等操作,通常配合其他类型装饰器使用。