Изучите использование и разработку декораторов параметров и подготовьтесь к декораторам методов.

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

В этой статье мы рассмотрим разработку декораторов для параметров метода в TypeScript. Вот как на практике выглядят декораторы параметров:

@ClassDecorator() 
class A { 
    ...
    @MethodDecorator()
    fly(
         @ParameterDecorator(?? optional parameters)
         meters: number
    ) {
         // code
    }
 ...
}

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

Чтобы использовать декораторы, в TypeScript должны быть включены две функции, поэтому обязательно ознакомьтесь со статьей Введение в декораторы в этой серии.

Эта статья является частью серии:

Изучение декораторов параметров в TypeScript

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

Сигнатура для функций декоратора параметров:

  1. Либо функция-конструктор класса для статического члена, либо прототип класса для члена-экземпляра.
  2. Строка, задающая имя свойства
  3. Порядковый индекс параметра в списке параметров функции

Первые два аргумента аналогичны аргументам, предоставляемым функциям-декораторам свойств и методов доступа. Третий относится к позиции в списке параметров метода класса:

class ClassName {
     method(param0, param1, param2, param3, ...) {
        ..
     }
}

Параметры нумеруются индексом, начинающимся с 0, как показано здесь. Третий аргумент — это просто целое число, задающее индекс, например 0, 1, 2 и т. д.

В других декораторах объект с именем PropertyDescriptor представлен в третьем аргументе. С этим дескриптором можно делать много интересных вещей, но он недоступен для декораторов параметров.

Простой пример декораторов параметров в TypeScript

Чтобы увидеть, как это работает, давайте попробуем простой пример, который печатает полученные значения:

import * as util from 'util';  
function logParameter(target: Object, 
                      propertyKey: string | symbol,
                      parameterIndex: number) {
      console.log(`logParameter ${target} ${util.inspect(target)} ${String(propertyKey)} ${parameterIndex}`);
}
class ParameterExample {
      member(@logParameter x: number,
             @logParameter y: number) {
         console.log(`member ${x} ${y}`);
      }
}
const pex = new ParameterExample();
pex.member(2, 3); 
pex.member(3, 5); 
pex.member(5, 8);

Тип для target указан как общий Object. propertyKey — это имя функции, в данном случае member. parameterIndex — это целое число, начинающееся с 0, перечисляющее параметр, к которому привязан этот декоратор.

Запустив этот скрипт, мы получим следующий вывод:

$ npx ts-node lib/parameters/parameters.ts  
logParameter [object Object] {} member 1 
logParameter [object Object] {} member 0 
member 2 3 
member 3 5
 member 5 8

target оказывается анонимным Объектом. В противном случае значения ключа и индекса соответствуют ожидаемым.

Обратите внимание, что у декоратора параметров нет возможности выполнять код для экземпляров объекта, содержащего параметр. Вместо этого область его воздействия приходится на время создания объекта класса. В отличие от других декораторов, нам не дается какой-либо объект, который можно изменить, чтобы повлиять на поведение экземпляров класса.

Вместо этого декоратор параметра служит главным образом маркером, добавляющим информацию к параметру метода. Официальная документация ясно говорит об этом:

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

В большинстве случаев выполнение каких-либо важных действий с помощью декоратора параметров требует сотрудничества с другими декораторами. Например, декоратор параметров может хранить данные с помощью API-интерфейсов Reflection и Reflection Metadata, а другие декораторы могут использовать эти данные при реализации других функций.

Подробный анализ данных, доступных декораторам параметров

Существует потенциальная ценность глубокого осмотра объекта target. Из документации TypeScript мы видим, что это объект класса, так что давайте проверим, что это значит.

В пакете decorator-inspectors есть декоратор, который мы можем использовать. Этот пример получен из этого декоратора:

export function ParameterInspector(target: Object,
                                   propertyKey: string | symbol,
                                   parameterIndex: number) {
      const ret = {
         target, propertyKey, parameterIndex,
         ownKeys: Object.getOwnPropertyNames(target),
         members: {}
      };
      for (const key of Object.getOwnPropertyNames(target)) {
         ret.members[key] = {
             obj: target[key],
             descriptor: util.inspect(
                 Object.getOwnPropertyDescriptor(target, key)
             )
         };
     }
     console.log(ret);
}

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

Если мы заменим @ParameterInspector на @logInspector в приведенном выше примере, мы получим следующий вывод:

{
   target: {},
   propertyKey: 'member',
   parameterIndex: 0,
   ownKeys: [ 'constructor', 'member' ],
   members: {
     constructor: {
       obj: [class ParameterExample],
       descriptor: '{\n' +
         '  value: [class ParameterExample],\n' +
         '  writable: true,\n' +
         '  enumerable: false,\n' +
         '  configurable: true\n' +
         '}'
     },
     member: {
       obj: [Function: member],
       descriptor: '{\n' +
         '  value: [Function: member],\n' +
         '  writable: true,\n' +
         '  enumerable: false,\n' +
         '  configurable: true\n' +
         '}'
     }
   }
}

Действительно, из этого становится ясно, что target — это класс, показанный выше. Список, возвращаемый getOwnPropertyNames, представляет собой имена методов, включая constructor в качестве метода, хотя мы явно не создавали конструктор. Доступен даже PropertyDescriptor.

Регистрация декораторов параметров в фреймворке

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

Примером может быть метод обработки маршрута в классе, представляющем маршрутизатор в веб-инфраструктуре, такой как Express. Мы можем захотеть внедрить его в значения параметров, полученные из строки запроса в URL-адресе, или параметры тела в запросе POST.

@Router('/blog') 
class BlogRouter {
     @Get('/view/:id')
     viewPost(req, res, next,
         @URLParam('id') id: string
     ) {
         // handle route
     } 
}

В библиотеке декораторов Reflet для Express есть такие декораторы параметров, как и другие декораторы, показанные здесь. Для этого примера давайте реализуем только ту часть URLParam, которая записывает некоторые данные. Когда мы будем работать с декораторами методов, мы создадим более полный пример, где декораторы методов и параметров работают вместе.

const registered = [];  
function URLParam(id: string) {
     return (target: Object,
         propertyKey: string | symbol,
         parameterIndex: number) => {
          const topush = {
             target, propertyKey, parameterIndex, urlparam: id,
             ownKeys: Object.getOwnPropertyNames(target),
             function: target[propertyKey],
             // funcDescriptor: Object.getOwnPropertyDescriptor(target, propertyKey)
         };
         registered.push(topush);
     } 
}  
class BlogRouter {
      viewPost(req, res, next,
         @URLParam('id') id: string
     ) {
         console.log(`viewPost`);
     }
      viewComments(req, res, next,
                 @URLParam('id') id: string,
                 @URLParam('commentID') commentID: string
     ) {
         console.log(`viewComments`);
     } 
}  
console.log(registered);

URLParam — это функция дескриптора параметра, которая собирает некоторые данные о декораторе параметра и методе, содержащем этот параметр. Он сохраняет это в массив с намерением, чтобы другие декораторы или фреймворк использовали эти данные для создания чего-то полезного. При обсуждении API отражения и метаданных мы увидим более практичный способ хранения этого массива данных.

В классе BlogRouter у нас есть два метода с несколькими параметрами, разделенными между ними, а некоторые параметры имеют @URLParam декораторов.

Затем мы можем запустить скрипт следующим образом:

$ npx ts-node lib/parameters/urlparam.ts 
[
   { 
     target: {},
     propertyKey: 'viewPost',
     parameterIndex: 3,
     urlparam: 'id',
     ownKeys: [ 'constructor', 'viewPost', 'viewComments' ],
     function: [Function: viewPost]
   },
   {
     target: {},
     propertyKey: 'viewComments',
     parameterIndex: 4,
     urlparam: 'commentID',
     ownKeys: [ 'constructor', 'viewPost', 'viewComments' ],
     function: [Function: viewComments]
   },
   {
     target: {},
     propertyKey: 'viewComments',
     parameterIndex: 3,
     urlparam: 'id',
     ownKeys: [ 'constructor', 'viewPost', 'viewComments' ],
     function: [Function: viewComments]
   } 
]

Это дает нам три соответствующих объекта данных. Поле propertyKey содержит имя метода, содержащего параметр, а поле parameterIndex содержит его индекс в списке параметров. Затем мы записываем в urlparam, какой элемент нужно получить по URL-адресу. Затем мы записываем список имен функций, а также объект функции для метода, потому что они могут быть полезны.

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

Краткое содержание

Мы можем прикреплять декораторы к параметрам метода. Это означает, что мы можем записывать информацию о декораторах, прикрепленных к каждому параметру, а затем что-то делать с этими данными.

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

об авторе

Дэвид Херрон: Дэвид Херрон — писатель и инженер-программист, занимающийся вопросами разумного использования технологий. Его особенно интересуют экологически чистые энергетические технологии, такие как солнечная энергия, энергия ветра и электромобили. Дэвид почти 30 лет работал в Силиконовой долине над программным обеспечением, начиная от систем электронной почты и заканчивая потоковым видео и языком программирования Java, и опубликовал несколько книг о программировании Node.js и электромобилях.

Первоначально опубликовано на https://techsparx.com.

Дополнительные материалы на plainenglish.io. Подпишитесь на нашу бесплатную еженедельную рассылку новостей. Получите эксклюзивный доступ к возможностям написания и советам в нашем сообществе Discord.