打字稿装饰器不适用于箭头函数

Shr*_*til 5 javascript decorator typescript ecmascript-6 typescript-decorator

我有一个 typescript 装饰器工厂,它控制台记录执行函数所花费的总时间、实际函数执行结果和传递给装饰器的参数。

例如

export function performaceLog(...args: any[]) {
return (target: Object, key: string, descriptor: TypedPropertyDescriptor<any>) => {
    var msg = '';
    if (args.length != 0) {
        msg = args.map(arg => arg).join(' ');
    }

    if (descriptor === undefined) {
        descriptor = Object.getOwnPropertyDescriptor(target, key);
    }

    if (typeof descriptor.value !== 'function') {
        throw new SyntaxError('Only functions can be used with log decorators');
    }

    var originalMethod = descriptor.value.bind(target);

    descriptor.value = function() {
        var funcArgs: any = [];
        for (var i = 0; i < arguments.length; i++) {
            funcArgs[i - 0] = arguments[i];
        }
        var startTime = performance.now();
        console.log(`Begin function ${key} with params (${funcArgs}) : ${msg}`);
        var result = originalMethod.apply(this, funcArgs);
        var endTime = performance.now();
        console.log(`End function ${key}. Execution time: ${endTime - startTime} milliseconds. Return result : ${result}`);
        return result;
    };
    return descriptor;
};
}
Run Code Online (Sandbox Code Playgroud)

我将上述装饰器与类中存在的函数一起使用:(考虑到我的类还有其他方法,如 ctor、get、set 和 utils)。

class LoggerService {
    @performaceLog('validate', 'message')
    handleMessage(message) {
        console.log(message);
        return true;
    };
}
Run Code Online (Sandbox Code Playgroud)

函数调用如下所示:

handleMessage('decoratorValidation');
Run Code Online (Sandbox Code Playgroud)

这给了我完美的输出:

Begin function handleMessage with params (decoratorValidation) : validate message     
decoratorValidation
End function handleMessage. Execution time: 0.3000000142492354 milliseconds. 
Return result : true
Run Code Online (Sandbox Code Playgroud)

但是当我更改函数 handleMessage 以支持箭头格式(ES6)时,它会抛出一个错误:

@performaceLog('validate', 'message')
handleMessage = message => {
    console.log(message);
    return true;
};
Run Code Online (Sandbox Code Playgroud)

错误信息:

Unable to resolve signature of property decorator when called as an expression.
Run Code Online (Sandbox Code Playgroud)

我在 tsconfig.json 中正确设置了所有参数。我的整个项目都支持“ES6”目标,并且我希望装饰器支持箭头功能。

Est*_*ask 8

performaceLog应该只使用原型方法,因为它依赖于descriptor,这应该是可选的。

descriptorhandleMessage = message => ...类字段的情况下没有,因为它不存在于类原型中。类字段只是this在构造函数中分配给。

由于相同的原因,这条线将不起作用:

descriptor = Object.getOwnPropertyDescriptor(target, key);
Run Code Online (Sandbox Code Playgroud)

为了修补装饰器中的箭头方法,应在类原型上设置陷阱。这是一个可以与原型和实例方法一起使用的通用装饰器示例;它使用get/set来捕获适当的this上下文并将装饰函数缓存到patchFn变量中。无论descriptor参数如何,它都会返回一个描述符:

function universalMethodDecorator(target: any, prop: string, descriptor?: TypedPropertyDescriptor<any>): any {
    let fn;
    let patchedFn;

    if (descriptor) {
        fn = descriptor.value;
    }

    return {
        configurable: true,
        enumerable: false,
        get() {
            if (!patchedFn) {
                patchedFn = (...args) => fn.call(this, ...args);
            }
            return patchedFn; 
        },
        set(newFn) {
            patchedFn = undefined;
            fn = newFn;
        }
    };

}
Run Code Online (Sandbox Code Playgroud)

这仅适用于 TypeScript 装饰器。Babel遗留装饰器的行为可能有所不同。

正如在这个答案中解释的那样,出于多种原因,原型方法可能比实例方法更受欢迎。原因之一是它们可以无缝装饰,因为装饰器应用于类原型。箭头方法唯一真正的好处是它自然绑定到类实例,但是由于装饰器已经在使用中,如果需要,原型方法可以在装饰器中绑定(这universalMethodDecorator基本上是这样做的;它是箭头方法的 noop)。