聊一聊 TypeScript 的装饰器

2,465 阅读2分钟

装饰器是一种特殊类型的声明,它能够被附加到类声明,方法, 访问符,属性或参数上。 装饰器使用 @expression 这种形式,expression 求值后必须为一个函数,它会在运行时被调用,被装饰的声明信息做为参数传入。

(一)使用

注意:装饰器是一项实验性特性,在未来的版本中可能会发生改变。

在 TypeScript 中装饰器还属于实验性语法,你必须在命令行或 tsconfig.json 里启用 experimentalDecorators 编译器选项:

  • 命令行:
tsc --target ES5 --experimentalDecorators
  • tsconfig.json:
{
    "compilerOptions": {
        "target": "ES5",
        "experimentalDecorators": true
    }
}

1、装饰类

针对类的修饰,会接受一个参数即类对象本身,下文通过对类添加静态属性实现。

@testable
class Demo{}

function testable(target) {
    target.isTestable=true
}

console.log(Demo.isTestable) // true

2、 装饰类方法

针对类方法修饰,函数会接收3个参数:

  • 1、target:当前对象的原型
  • 2、key:当前方法名
  • 3、descriptor:方法的属性描述符
// descriptor对象原来的值如下
// {
//   value: specifiedFunction,
//   enumerable: false,
//   configurable: true,
//   writable: true
// };

// target:在方法中的target指向类的prototype
function readonly(target,key,descriptor){
    descriptor.writable=false
    return descriptor
}

class Demo {
    @readonly
    print(){console.log(`a:${this.a}`)}
}

3、 装饰类属性

针对类属性装饰器,函数会接收2个参数:

  • 1、target:当前对象的原型
  • 2、key:当前属性名
function set(value: string) {
    return function (target: any, propertyName: string) {
        target[propertyName] = value;
    }
}

class Demo {
    @set("hello world") greeting: string;
}

console.log(new Demo().greeting);// hello world

(二)装饰器的执行顺序

多个修饰器的执行顺序是由外向内进入;再由内向外执行。

class Demo {
    @log(1)
    @log(2)
    method(){}
}

function log(id){
    console.log('id is ',id)
    return (target,property,descriptor)=>console.log('executed',id)
}

// 控制台输出
// id is  1
// id is  2
// executed 2
// executed 1

(三)实现几个简单的装饰器

本文用到的个小方法

// 返回空对象
export const noop = () => { };

/**
 * 判断对象类型
 * @param [obj: any] 参数对象
 * @returns String
 */
export function isType(obj) {
  return Object.prototype.toString.call(obj).replace(/^\[object (.+)\]$/, '$1').toLowerCase();
}

1、装饰器实现异常捕获

/**
 * 用于捕获Error
 * @tips 请注意,参数params中的回调函数不能使用箭头函数,否则将会引起上下文变动
 * @param [Object | Function] params 接收异常捕获回调,非必选参数
 * @param [params.callback] callback 默认接收Function类型的参数,或者接收 Object 对象中的callback方法
 * @returns descriptor
 */
export const DRCatchError = (params: any = noop) => (target, key, descriptor) => {
    const original = descriptor.value;
    let errorHandler: any = null;
    if (isType(params) === 'function') {
        errorHandler = params;
    } else if (isType(params) === 'object') {
        errorHandler = params.callback;
    }
    descriptor.value = async function () {
        try {
            await original.apply(this, arguments);
        } catch (error) {
            let resp = (error && error.response) || {};
            let errorData = resp.data || error.message;
            if (typeof errorHandler === 'function') {
                errorHandler.call(this, errorData);
            } else {
                console.error(error);
            }
        }
    };
    return descriptor;
};

2、装饰器实现防抖

/**
* 用于操作方法防抖
* @param [wait: number] 延时ms
* @param [immediate: boolean] 立即执行
* @returns descriptor
*/
export const DRDebounce = (wait: number, immediate: boolean = false) => (target, key, descriptor) => {
   let timeout: any;
   const original = descriptor.value;
   descriptor.value = function () {
       let later = () => {
           timeout = null;
           if (!immediate) {
               original.apply(this, arguments);
           }
       };
       let callNow = immediate && !timeout;
       clearTimeout(timeout);
       timeout = setTimeout(later, wait);
       if (callNow) {
           original.apply(this, arguments);
       }
   };
   return descriptor;
};

3、装饰器实现节流

/**
* 用于操作函数节流
*  @param [delay: number] 延时ms
*/
export const DRThrottle = (delay: number) => (target, key, descriptor) => {
   let last: any;
   let deferTimer: any;
   const original = descriptor.value;
   descriptor.value = function() {
       let now = +new Date();
       if (last && now < last + delay) {
           clearTimeout(deferTimer);
           deferTimer = setTimeout(() => {
               last = now;
               original.apply(this, arguments);
           }, delay);
       }else {
           last = now;
           original.apply(this, arguments);
       }
   };
   return descriptor;
};

(四)参考链接

(五)其他链接

微信公众号:前端杂论