TypeScript dekoratorlar


Dekoratorlar kuchli TypeScript xususiyati bo‘lib, u sizga metama’lumotlarni qo‘shish va dizayn vaqtida classlar va ularning a’zolarini o‘zgartirish imkonini beradi.

Ular Angular va NestJS kabi frameworklarda qaramlikni in’ektsiya qilish, marshrutlash va boshqalar uchun keng qo‘llaniladi.


ULASHISH

Dekoratorlarni yoqish

TypeScript-da dekorativlardan foydalanish uchun ularni tsconfig.json da yoqishingiz kerak:

tsconfig.json

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "commonjs",
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "strictPropertyInitialization": false
  },
  "include": ["src/**/*.ts"]
}

Eslatma: emitDecoratorMetadata opsiyasi TypeORM va class-validator kabi kutubxonalar tomonidan ishlatiladigan dekorativlar uchun turdagi metama’lumotlarni chiqarish uchun eksperimental yordamni ta’minlaydi.


Dekoratorlarning turlari

TypeScript turli xil deklaratsiyalarga qo‘llanilishi mumkin bo‘lgan bir necha turdagi dekorativlarni qo‘llab-quvvatlaydi:

Decorator Type Nimaga qo‘llaniladi Signature
Class Decorator Class declarations (constructor: Function) => void
Method Decorator Class methods (target: any, propertyKey: string, descriptor: PropertyDescriptor) => void
Property Decorator Class properties (target: any, propertyKey: string) => void
Parameter Decorator Method parameters (target: any, propertyKey: string, parameterIndex: number) => void

Class dekoratorlari

Class dekoratorlari class konstruktoriga qo‘llaniladi va class ta’rifini kuzatish, o‘zgartirish yoki almashtirish uchun ishlatilishi mumkin.

Ular misollar yaratilganda emas, balki class e’lon qilinganda chaqiriladi.

Asosiy class dekoratori

Ushbu oddiy dekorator class aniqlanganda qayd qiladi:

// A simple class decorator that logs class definition
function logClass(constructor: Function) {
  console.log(`Class ${constructor.name} was defined at ${new Date().toISOString()}`);
}

// Applying the decorator
@logClass
class UserService {
  getUsers() {
    return ['Alice', 'Bob', 'Charlie'];
  }
}

// Output when the file is loaded: "Class UserService was defined at [timestamp]"
O‘zingiz sinab ko‘ring »


Konstruktor modifikatsiyasi bilan class dekoratori

Ushbu misol xususiyatlar va usullarni qo‘shish orqali sinfni qanday o‘zgartirishni ko‘rsatadi:

// A decorator that adds a version property and logs instantiation
function versioned(version: string) {
  return function (constructor: Function) {
    // Add a static property
    constructor.prototype.version = version;
    
    // Store the original constructor
    const original = constructor;
    // Create a new constructor that wraps the original
    const newConstructor: any = function (...args: any[]) {
      console.log(`Creating instance of ${original.name} v${version}`);
      return new original(...args);
    };
    
    // Copy prototype so instanceof works
    newConstructor.prototype = original.prototype;
    return newConstructor;
  };
}

// Applying the decorator with a version
@versioned('1.0.0')
class ApiClient {
  fetchData() {
    console.log('Fetching data...');
  }
}

const client = new ApiClient();
console.log((client as any).version); // Outputs: 1.0.0
client.fetchData();
O‘zingiz sinab ko‘ring »

Muhrlangan class dekoratori

Ushbu dekorator yangi xususiyatlarni sinfga qo‘shishni oldini oladi va barcha mavjud xususiyatlarni sozlanmaydigan qilib belgilaydi:

function sealed(constructor: Function) {
  console.log(`Sealing ${constructor.name}...`);
  Object.seal(constructor);
  Object.seal(constructor.prototype);
}

@sealed
class Greeter {
  greeting: string;
  constructor(message: string) {
    this.greeting = message;
  }
  greet() {
    return `Hello, ${this.greeting}`;
  }
}

// This will throw an error in strict mode
// Greeter.prototype.newMethod = function() {}; // Error: Cannot add property newMethod
O‘zingiz sinab ko‘ring »

Class dekoratorlari haqida asosiy fikrlar

  • Class dekoratorlari namunalar yaratilganda emas, balki class e’lon qilinganda chaqiriladi
  • Ular class konstruktorini yagona parametr sifatida qabul qilishadi
  • Ular asl sinfni almashtirish uchun yangi konstruktor funksiyasini qaytarishi mumkin
  • Ular pastdan yuqoriga qarab bajariladi (eng ichki dekorator birinchi ishlaydi)
  • Ular jurnalga yozish, muhrlash, muzlatish yoki metama’lumotlarni qo‘shish uchun ishlatilishi mumkin

Dekorativ uslublar

Metod dekoratorlari metod ta’riflariga qo‘llaniladi va usul ta’riflarini kuzatish, o‘zgartirish yoki almashtirish uchun ishlatilishi mumkin.

Ular uchta parametrni oladi:

  1. target : Class prototipi (masalan, usullar) yoki konstruktor funksiyasi (statik usullar uchun)
  2. propertyKey : Usul nomi
  3. descriptor : Usul uchun xossa identifikatori

Vaqtni belgilash usuli

Ushbu dekorator usulning bajarilish vaqtini o‘lchaydi va qayd qiladi:

// Method decorator to measure execution time
function measureTime(
  target: any,
  propertyKey: string,
  descriptor: PropertyDescriptor
) {
  const originalMethod = descriptor.value;
  descriptor.value = function (...args: any[]) {
    const start = performance.now();
    const result = originalMethod.apply(this, args);
    const end = performance.now();
    console.log(`${propertyKey} executed in ${(end - start).toFixed(2)}ms`);
    return result;
  };
  return descriptor;
}

// Using the decorator
class DataProcessor {
  @measureTime
  processData(data: number[]): number[] {
    // Simulate processing time
    for (let i = 0; i < 100000000; i++) { /* processing */ }
    return data.map(x => x * 2);
  }
}

// When called, it will log the execution time
const processor = new DataProcessor();
processor.processData([1, 2, 3, 4, 5]);
O‘zingiz sinab ko‘ring »

Usul avtorizatsiya dekoratori

Ushbu misol uslublar dekoratorlari yordamida rolga asoslangan kirishni boshqarishni qanday amalga oshirishni ko‘rsatadi:

// User roles
type UserRole = 'admin' | 'editor' | 'viewer';

// Current user context (simplified)
const currentUser = {
  id: 1,
  name: 'John Doe',
  roles: ['viewer'] as UserRole[]
};

// Decorator factory for role-based access control
function AllowedRoles(...allowedRoles: UserRole[]) {
  return function (
    target: any,
    propertyKey: string,
    descriptor: PropertyDescriptor
  ) {
    const originalMethod = descriptor.value;
    descriptor.value = function (...args: any[]) {
      const hasPermission = allowedRoles.some(role =>
        currentUser.roles.includes(role)
      );
      if (!hasPermission) {
        throw new Error(
          `User ${currentUser.name} is not authorized to call ${propertyKey}`
        );
      }
      return originalMethod.apply(this, args);
    };
    return descriptor;
  };
}

// Using the decorator
class DocumentService {
  @AllowedRoles('admin', 'editor')
  deleteDocument(id: string) {
    console.log(`Document ${id} deleted`);
  }
  @AllowedRoles('admin', 'editor', 'viewer')
  viewDocument(id: string) {
    console.log(`Viewing document ${id}`);
  }
}

// Usage
const docService = new DocumentService();
try {
  docService.viewDocument('doc123'); // Works - viewer role is allowed
  docService.deleteDocument('doc123'); // Throws error - viewer cannot delete
} catch (error) {
  console.error(error.message);
}
// Change user role to admin
currentUser.roles = ['admin'];
docService.deleteDocument('doc123'); // Now works - admin can delete
O‘zingiz sinab ko‘ring »

Eskirish haqida ogohlantirish dekoratori

Ushbu dekorator kelajakdagi versiyada olib tashlanadigan usulga eskirish haqida ogohlantirish qo‘shadi:

function deprecated(message: string) {
  return function (
    target: any,
    propertyKey: string,
    descriptor: PropertyDescriptor
  ) {
    const originalMethod = descriptor.value;
    descriptor.value = function (...args: any[]) {
      console.warn(`Warning: ${propertyKey} is deprecated. ${message}`);
      return originalMethod.apply(this, args);
    };
    return descriptor;
  };
}

class PaymentService {
  @deprecated('Use processPaymentV2 instead')
  processPayment(amount: number, currency: string) {
    console.log(`Processing payment of ${amount} ${currency}`);
  }

  processPaymentV2(amount: number, currency: string) {
    console.log(`Processing payment v2 of ${amount} ${currency}`);
  } }

const payment = new PaymentService();
payment.processPayment(100, 'USD'); // Shows deprecation warning
payment.processPaymentV2(100, 'USD'); // No warning
O‘zingiz sinab ko‘ring »

Usul dekorativlari haqida asosiy fikrlar

  • Metod dekoratorlari usul chaqirilganda emas, balki aniqlanganda chaqiriladi
  • Ular usulning harakatini qo‘shimcha mantiq bilan o‘rash orqali o‘zgartirishi mumkin
  • Ular ro‘yxatga olish, tekshirish va avtorizatsiya kabi o‘zaro bog‘liq muammolar uchun ishlatilishi mumkin
  • Ular usulning xulq-atvorini o‘zgartirishga imkon beruvchi xususiyat deskriptorini oladi
  • Ular xususiyat deskriptorini (property descriptor) yoki undefined qaytarishi kerak (agar deskriptor o‘zgartirilmasa)

Ko‘chmas xususiyat dekorativlari

Xususiyat dekorativlari xususiyat deklaratsiyasiga qo‘llaniladi va xususiyat ta’riflarini kuzatish, o‘zgartirish yoki almashtirish uchun ishlatilishi mumkin.

Ular ikkita parametrni oladi:

  1. target : Class prototipi (masalan, xususiyatlar) yoki konstruktor funksiyasi (statik xususiyatlar uchun)
  2. propertyKey : Xususiyat nomi

Formatlangan xususiyat dekoratori

Ushbu dekorator o‘rnatilganda xususiyatni avtomatik ravishda formatlaydi:

// Property decorator to format a string property
function format(formatString: string) {
  return function (target: any, propertyKey: string) {
    let value: string;
    const getter = () => value;
    const setter = (newVal: string) => {
      value = formatString.replace('{}', newVal);
    };
    Object.defineProperty(target, propertyKey, {
      get: getter,
      set: setter,
      enumerable: true,
      configurable: true
    });
  };
}

class Greeter {
  @format('Hello, {}!')
  greeting: string;
}

const greeter = new Greeter();
greeter.greeting = 'World';
console.log(greeter.greeting); // Outputs: Hello, World!
O‘zingiz sinab ko‘ring »

Jurnal xususiyat dekoratori

Ushbu dekorator mulkka kirish va o‘zgarishlarni qayd qiladi:

function logProperty(target: any, propertyKey: string) {
  let value: any;
  const getter = function() {
    console.log(`Getting ${propertyKey}: ${value}`);
    return value;
  };

  const setter = function(newVal: any) {
    console.log(`Setting ${propertyKey} from ${value} to ${newVal}`);
    value = newVal;
  };

  Object.defineProperty(target, propertyKey, {
    get: getter,
    set: setter,
    enumerable: true,
    configurable: true
  });
}

class Product {
  @logProperty
  name: string;
  @logProperty
  price: number;

  constructor(name: string, price: number) {
    this.name = name;
    this.price = price;
  }
}

const product = new Product('Laptop', 999.99);
product.price = 899.99; // Logs: Setting price from 999.99 to 899.99
console.log(product.name); // Logs: Getting name: Laptop
O‘zingiz sinab ko‘ring »

Kerakli xususiyat dekoratori

Ushbu dekorator xususiyat o‘rnatilishi kerakligini ta’minlaydi:

function required(target: any, propertyKey: string) {
  let value: any;

  const getter = function() {
    if (value === undefined) {
      throw new Error(`Property ${propertyKey} is required`);
    }
    return value;
  };

  const setter = function(newVal: any) {
    value = newVal;
  };

  Object.defineProperty(target, propertyKey, {
    get: getter,
    set: setter,
    enumerable: true,
    configurable: true
  });
}

class User {
  @required
  username: string;
  @required
  email: string;
  age?: number;

  constructor(username: string, email: string) {
    this.username = username;
    this.email = email;
  }
}

const user1 = new User('johndoe', 'john@example.com'); // Works
// const user2 = new User(undefined, 'test@example.com'); // Throws error: Property username is required
// const user2 = new User('johndoe', undefined); // Throws error: Property email is required
O‘zingiz sinab ko‘ring »

Xususiyat dekorativlari haqida asosiy fikrlar

  • Xususiyat dekoratorlari mulkka kirishda emas, balki aniqlanganda chaqiriladi
  • Ular uslub dekorativlari kabi xususiyat tavsifini olmaydilar
  • Xususiyat xatti-harakatlarini o‘zgartirish uchun siz Object.defineProperty dan foydalanishingiz kerak
  • Ular ko‘pincha metama’lumotlarni aks ettirish yoki mulkka kirishni o‘zgartirish uchun ishlatiladi
  • Ular murakkabroq xatti-harakatlar uchun boshqa dekorativlar bilan birlashtirilishi mumkin

Parametr dekorativlari

Parametr dekoratorlari konstruktor yoki usulda parametr e’lonlariga qo‘llaniladi.

Ular uchta parametrni oladi:

  1. target : Class prototipi (masalan, usullar) yoki konstruktor funksiyasi (statik usullar uchun)
  2. propertyKey : Usul nomi (yoki konstruktor parametrlari uchun aniqlanmagan)
  3. parameterIndex : Funksiya parametrlari ro‘yxatidagi parametrning tartib indeksi

Parametrlarni tekshirish dekoratori

Ushbu dekorator usul parametrlarini tasdiqlaydi:

function validateParam(type: 'string' | 'number' | 'boolean') {
  return function (target: any, propertyKey: string | symbol, parameterIndex: number) {
    const existingValidations: any[] = Reflect.getOwnMetadata('validations', target, propertyKey) || [];
    existingValidations.push({ index: parameterIndex, type });
    Reflect.defineMetadata('validations', existingValidations, target, propertyKey);
  };
}

function validate(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.value;
  descriptor.value = function (...args: any[]) {
    const validations: Array<{index: number, type: string}> =
      Reflect.getOwnMetadata('validations', target, propertyKey) || [];

    for (const validation of validations) {
      const { index, type } = validation;
      const param = args[index];
      let isValid = false;

      switch (type) {
        case 'string':
          isValid = typeof param === 'string' && param.length > 0;
          break;
        case 'number':
          isValid = typeof param === 'number' && !isNaN(param);
          break;
        case 'boolean':
          isValid = typeof param === 'boolean';
      }

      if (!isValid) {
        throw new Error(`Parameter at index ${index} failed ${type} validation`);
      }
    }

    return originalMethod.apply(this, args);
  };
  return descriptor;
}

class UserService {
  @validate
  createUser(
    @validateParam('string') name: string,
    @validateParam('number') age: number,
    @validateParam('boolean') isActive: boolean
  ) {
    console.log(`Creating user: ${name}, ${age}, ${isActive}`);
  }
}

const service = new UserService();
service.createUser('John', 30, true); // Works
// service.createUser('', 30, true); // Throws error: Parameter at index 0 failed string validation
// service.createUser('John', 'thirty', true); // Throws error: Parameter at index 1 failed number validation
O‘zingiz sinab ko‘ring »

Parametr dekorativlari haqida asosiy fikrlar

  • Parametr dekoratorlari chaqirilganda emas, balki usul aniqlanganda chaqiriladi
  • Ular ko‘pincha kesishgan tashvishlarni amalga oshirish uchun uslublar dekoratorlari bilan ishlatiladi
  • Ulardan reflect-metadata kutubxonasi bilan metama’lumotlarni saqlash va olish uchun foydalanish mumkin
  • Ular odatda qaramlik in’ektsiya ramkalarida qo‘llaniladi
  • Ular parametr indeksini oladilar, undan ish vaqtida parametr qiymatiga kirish uchun foydalanish mumkin

Dekorativ zavodlar

Dekorator zavodlari dekorativ funksiyani qaytaruvchi funksiyalardir.

Ular sizga parametrlarni o‘tkazish orqali dekorativlarni sozlash imkonini beradi.

Bu sizning dekorativlaringizni yanada moslashuvchan va qayta foydalanishga imkon beradi.

Sozlanishi mumkin bo‘lgan logging dekoratori

Ushbu misol turli jurnal darajalari bilan moslashtirilishi mumkin bo‘lgan sozlanishi mumkin bo‘lgan logging dekoratorini qanday yaratishni ko‘rsatadi:

// Decorator factory that accepts configuration
function logWithConfig(config: { level: 'log' | 'warn' | 'error', message?: string }) {
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value;
    descriptor.value = function (...args: any[]) {
      const { level = 'log', message = 'Executing method' } = config;
      console[level](`${message}: ${propertyKey}`, { arguments: args });
      const result = originalMethod.apply(this, args);
      console[level](`${propertyKey} completed`);
      return result;
    };
    return descriptor;
  };
}

class PaymentService {
  @logWithConfig({ level: 'log', message: 'Processing payment' })
  processPayment(amount: number) {
    console.log(`Processing payment of $${amount}`);
  }
}
O‘zingiz sinab ko‘ring »

Baholash tartibi

Ushbu misolda bir nechta dekorativlar uchun baholash tartibi ko‘rsatilgan:

Agar deklaratsiyaga bir nechta dekoratorlar qo‘llanilsa, ular quyidagi tartibda baholanadi:

  1. Har bir misol a’zosi uchun Parametr dekoratorlari, so‘ngra Metod, Aksessuar yoki Xususiyat dekoratorlari qo‘llaniladi.
  2. Har bir statik element uchun Parametr dekoratorlari, undan keyin Metod, Aksessuar yoki Xususiyat dekoratorlari qo‘llaniladi.
  3. Konstruktor uchun Parametr Dekoratorlari qo‘llaniladi.
  4. Class uchun dekoratorlar qo‘llaniladi.
function first() {
  console.log('first(): factory evaluated');
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    console.log('first(): called');
  };
}

function second() {
  console.log('second(): factory evaluated');
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    console.log('second(): called');
  };
}

class ExampleClass {
  @first()
  @second()
  method() {}
}

// Output:
// second(): factory evaluated
// first(): factory evaluated
// first(): called
// second(): called
O‘zingiz sinab ko‘ring »

Haqiqiy dunyo misollari

Dekoratorlar bilan API tekshiruvi

Bu misol NestJS yoki Express-dagi kabi oddiy API kontroller yaratish uchun dekorativlardan qanday foydalanish mumkinligini ko‘rsatadi:

// Simple decorator implementations (simplified for example)
const ROUTES: any[] = [];

function Controller(prefix: string = '') {
  return function (constructor: Function) {
    constructor.prototype.prefix = prefix;
  };
}

function Get(path: string = '') {
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    ROUTES.push({
      method: 'get',
      path,
      handler: descriptor.value,
      target: target.constructor
    });
  };
}

// Using the decorators
@Controller('/users')
class UserController {
  @Get('/')
  getAllUsers() {
    return { users: [{ id: 1, name: 'John' }] };
  }

  @Get('/:id')
  getUserById(id: string) {
    return { id, name: 'John' };
  }
}

// Simulate route registration
function registerRoutes() {
  ROUTES.forEach(route => {
    const prefix = route.target.prototype.prefix || '';
    console.log(`Registered ${route.method.toUpperCase()} ${prefix}${route.path}`);
  });
}

registerRoutes();
// Output:
// Registered GET /users
// Registered GET /users/:id
O‘zingiz sinab ko‘ring »

Eng yaxshi amaliyotlar

Dekoratorning eng yaxshi amaliyotlari

Dekoratorlar bilan ishlashda quyidagi eng yaxshi amaliyotlarga amal qiling:

  1. Keep decorators focused : Har bir dekorator bitta mas’uliyatga ega bo‘lishi kerak.
  2. Document behavior : Dekoratorlaringiz nima qilayotganini va ularda yuzaga kelishi mumkin bo‘lgan har qanday nojo‘ya ta’sirlarni aniq hujjatlashtiring.
  3. Use decorator factories : Yaxshiroq qayta foydalanish uchun dekorativlarni zavodlar yordamida sozlanishi mumkin.
  4. Consider performance : Ishlash vaqtiga qo‘shimcha xarajatlar qo‘shadigan dekoratorlar bilan ishlashga ta’sir qilishiga e’tibor bering.
  5. Type safety : Iloji bo‘lsa, dekorativlar turini xavfsiz qilish uchun TypeScript turi tizimidan foydalaning.
  6. Error handling : Dekoratorlarda xatoliklarni to‘g‘ri hal qilish.
  7. Testing : Dekoratorlaringiz kutilganidek ishlashiga ishonch hosil qilish uchun birlik testlarini yozing.
  8. Metadata : Ish vaqti turi ma’lumotlarini talab qiladigan kengaytirilgan stsenariylar uchun reflect-metadata dan foydalaning.

Umumiy tuzoqlar

Oddiy dekorativ tuzoqlar

Dekoratorlar bilan ishlashda quyidagi keng tarqalgan xatolardan qoching:

  • Forgetting to enable decorators : tsconfig.json da experimentalDecorators yoqilganligiga ishonch hosil qiling.
  • Incorrect decorator signature : Har bir dekorator turi o‘ziga xos imzoga ega. Noto‘g‘ri parametrlardan foydalanish ish vaqti xatolariga olib kelishi mumkin.
  • Decorator evaluation order : Dekoratorlar har bir deklaratsiya uchun pastdan yuqoriga qarab baholanadi.
  • Property initialization : Xususiyat dekoratorlari namuna xususiyatlari ishga tushirilgunga qadar ishlaydi.
  • Metadata reflection : Dekorator metama’lumotlaridan foydalanishda reflect-metadata import qilishni unutish.
  • Performance overhead : Ishlash uchun muhim kodda ishlash vaqtiga katta yuk qo‘shadigan dekorativlar bilan ehtiyot bo‘ling.
  • Browser compatibility : Dekoratorlar 3-bosqich taklifidir va eski brauzerlar uchun transpilyatsiyani talab qilishi mumkin.

Misol: xususiyat dekoratori

function readonly(target: any, propertyKey: string) {
  Object.defineProperty(target, propertyKey, {
    writable: false
  });
}

class Person {
  @readonly
  name = "John";
}

Misol: Parametr dekoratori

function logParameter(target: any, propertyKey: string, parameterIndex: number) {
  console.log(`Parameter in ${propertyKey} at index ${parameterIndex}`);
}

class Demo {
  greet(@logParameter message: string) {
    return message;
  }
}

Buni tsconfig.json ga qo‘shish orqali dekorativlarni yoqing:

Misol

{
  "compilerOptions": {
    "experimentalDecorators": true
  }
}

Dekoratorlar qayerda ishlatiladi?

  • Angular: Komponentlar, xizmatlar, modullar va boshqalar uchun.
  • NestJS: Kontrollerlar, provayderlar, marshrutlar va boshqalar uchun.



W3Schools Pathfinder

Yutuqlaringizni kuzating – bu bepul!