Typescript Decorators: добавьте поле экземпляра в класс для использования в том же классе - PullRequest
1 голос
/ 29 марта 2020

Сейчас я изучаю декораторы Typescript. Моя первая цель - немного воспроизвести то, что @ Slf4J из Project Lombok делает в Java, в Typescript. Идея состоит в том, чтобы аннотировать / украшать класс, например, @logger, чтобы получить поле log типа LogUtil внутри этого же класса, чтобы вызвать, например, log.info ().

LogUtil class :

export class LoggerUtil {
    logLevel: LogLevel;

    constructor(logLevel: LogLevel) {
        this.logLevel = logLevel;
    }

    error(className: string, message: string) {
        if (this.logLevel >= LogLevel.ERROR) {
            console.error(`${new Date()} [ERROR] ${className}: ${message}`);
        }
    }

    warn(className: string, message: string) {
        if (this.logLevel >= LogLevel.WARN) {
            console.log(`${new Date()} [WARN] ${className}: ${message}`);
        }
    }

    log(className: string, message: string): void {
        console.log(`${new Date()} [LOG] ${className} ${message}`)
    }

    info(className: string, message: string): void {
        if (this.logLevel >= LogLevel.INFO) {
            console.log(`${new Date()} [INFO] ${className}: ${message}`)
        }
    }

    call(className: string, message: string) {
        if (this.logLevel >= LogLevel.INFO) {
            console.log(`${new Date()} [CALL] ${className}.${message}`)
        }
    }

    debug(className: string, message: string) {
        if (this.logLevel >= LogLevel.DEBUG) {
            console.log(`${new Date()} [DEBUG] ${className}: ${message}`)
        }
    }
}

Перечисление LogLevel:

export enum LogLevel {
    ERROR = 0,
    WARN = 1,
    INFO = 2,
    DEBUG = 3
}

Пример класса с использованием декоратора @logger для получения экземпляра LoggerUtil в качестве журнала

@logger
export class SomeService {

    exampleFunction() {
        log.info("exampleFunction called")
    }
}

Я сейчас пытаюсь сделать это с декораторами уровня класса. Здесь я пытаюсь делать разные вещи:

Использование API Reflect для определения свойства класса. Здесь я даже не уверен, работает ли это вообще.

export function logger() {
    return function(target: Function) {
        Reflect.defineProperty(target, "log", { value: new LoggerUtil(LogLevel.DEBUG) } )
    }
}

Использование прототипа класса для определения свойства:

export function logger() {
    return function(target: Function) {
        target.prototype.log = new LoggerUtil(LogLevel.DEBUG);
    }
}

При каждом подходе я получаю журнал «Не удается найти имя» '"при обращении к экземпляру журнала в Сервисе:

@logger
export class SomeService {

    exampleFunction() {
        log.info("exampleFunction called") // Cannot find name 'log'
    }
}

Возможна ли вообще моя идея? Есть что-то фундаментальное, чего мне не хватает?

Большое спасибо за любые отзывы!

1 Ответ

0 голосов
/ 07 апреля 2020

Я нашел подход, который хотя бы немного удовлетворяет мои потребности:

Я пошел с наследованием (которого я хотел бы избежать). Пример ниже:

@logger
export class SomeService extends Logger {

    constructor() {
        super()
    }

    exampleFunction() {
        log.info("exampleFunction called")
    }
}

Тогда расширяемый мной суперкласс выглядит следующим образом:

@logger
export class Logger {
    log: LoggerUtil;
}

И, наконец, декоратор выглядит так:

export function logger<T extends {new(...args:any[]):{}}>(constructor:T) {
    return class extends constructor {
        log = new LoggerUtil(LogLevel.DEBUG);
    }
}

Моя идея состояла в том, чтобы добавить поле log к SomeService через наследование. Чтобы инициализировать это поле сейчас, я использовал декоратор суперкласса. Декоратор сам возвращает класс, который расширяет класс декоратора инициализированным полем. Таким образом создается следующий граф наследования:

@ logger (Decorator) -> Logger (суперкласс) -> SomeService.

Я мог бы инициализировать поле log внутри самого суперкласса Logger . Тем не менее, это было сделано для того, чтобы заглянуть в декораторы и, надеюсь, в конечном итоге стереть суперкласс.

В качестве справки я хочу указать на документацию Typescript о том, как переписать конструктор.

...