Понимание декораторов TypeScript

JavaScript — потрясающий язык программирования. И TypeScript проделал большую работу, заполнив пробелы JavaScript. Он не только добавляет типы, но и реализует несколько дополнительных функций, которых пока нет в JavaScript . Одни из них – декораторы.

Что такое декоратор?

Декораторы уже давно присутствуют в языках программирования. Определения различаются, но вкратце декоратор — это шаблон в JavaScript, который используется для обертывания чего-то, чтобы изменить его поведение.

Как в JavaScript, так и в TypeScript это экспериментальная функция. В JavaScript это все еще предложение стадии 2, и вы можете использовать его только через транспиляторы, такие как Babel.

Мы решили объяснить декораторы TypeScript, потому что в TypeScript они стандартизированы, и в любом случае они в основном одинаковы.

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

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

const myDecorator = (thing: Function) => {
    // return something
}

@myDecorator
class Thing {

}

Сначала мы определяем функцию myDecorator, а затем «декорируем» переменную ( Thing в данном случае наш класс) с помощью декоратора. Декоратор может возвращать практически все, что угодно, но в большинстве случаев он используется для установки свойств класса и т. д. Вот пример из реальной жизни:

const defaultGun = (gun: Function) => class extends gun {
    ammo = 10
}

@defaultGun
class Gun {

}

Теперь Gun будет иметь ammo свойство по умолчанию.

const gun = new Gun()
console.log(gun.ammo) // => 10

Функции декорирования

Еще одно место, где мы можем использовать декораторы, — это методы класса. На этот раз декоратор получает три аргумента.

const myDecorator = (parent: Function, prop: string, descriptor: PropertyDescriptor) => {
    // return something
}

class Gun {
    @myDecorator
    fire() {
        console.log('Firing in 3... 2... 1... 🔫')
    }
}

Первый параметр содержит класс, в котором находится декорированная вещь (в нашем случае Gun). Второй параметр — это имя декорируемого свойства (в нашем случае fire). Последним является дескриптор свойства, который является результатом Object.getOwnPropertyDescriptor(parent[prop])

Характеристики

Вы также можете декорировать свойства. Это почти то же самое, что и декораторы функций, за исключением того, что третьего параметра нет:

const myDecorator = (parent: Function, prop: string) => {
    // return something
}

Больше мест для украшения

Вы также можете декорировать в большем количестве мест. Ознакомьтесь с документацией , чтобы узнать больше.

Случаи использования

Есть много применений для декораторов. Мы рассмотрим некоторые здесь.

Рассчитать производительность

class Gun {
    @time
    fire() {
        console.log('Firing in 3... 2... 1... 🔫')
    }
}

time может быть функцией, которая вычисляет время выполнения.

Фабрика декораторов

Декораторы также могут быть фабриками, которые возвращают функцию, которая является фактическим декоратором. Это может быть полезно, когда вы хотите, чтобы ваши декораторы нуждались в аргументах.

// The decorator factory
const change = value => {
    // The factory will return a new handler
    return (target, prop) => {
        // We replace the old value with a new one
        Object.defineProperty(target, prop, {value})
    }
}

Затем при «декорировании» нам просто нужно украсить как функцию:

class Gun {
    @change(20)
    ammo = 10
}

const gun = new Gun();
console.log(gun.ammo) // => 20

Практический пример: обработка ошибок

Давайте используем то, что мы узнали, для решения реальной проблемы.

class Gun {
    ammo = 0

    fireTwice() {
        console.log('Firing in 3... 2... 1... 🔫')
    }
}

Чтобы выстрелить дважды, нам нужно как минимум 2 патрона. Мы можем проверить это с помощью декоратора:

const minAmmo = (ammo: number) => (
    target: Object,
    prop: string,
    descriptor: PropertyDescriptor
) => {
    const original = descriptor.value;

    descriptor.value = function (...args) {
        if (this.ammo >= ammo) original.apply(this);
        else console.log('Not enough ammo!');
    }

    return descriptor;
}

minAmmo это фабричный декоратор, который принимает параметр ammo, который представляет собой минимальный необходимый боезапас.

Мы можем реализовать его в нашем Gun классе.

class Gun {
    ammo = 0

    @minAmmo(2)
    fireTwice() {
        console.log('Firing in 3... 2... 1... 🔫')
    }
}

Теперь, если вы убежите fireTwice(), он не выстрелит, потому что у нас недостаточно боеприпасов.

Хорошо, что мы можем просто повторно использовать это, не переписывая оператор if. Предположим, нам нужен fireOnce метод. Мы можем легко реализовать это.

class Gun {
    ammo = 0

    @minAmmo(2)
    fireTwice() {
        console.log('Firing twice in 3... 2... 1... 🔫')
    }

    @minAmmo(1)
    fireOnce() {
        console.log('Firing once in 3... 2... 1... 🔫')
    }
}

Этот вид декоратора может быть очень полезным для аутентификации, авторизация и других приятных мелочах. Надеемся что теперь у вас есть понимание о декораторах в TypeScript. Спасибо за прочтение.