Mundarija (31)
- Bu darsda
- 1. Nega bu kerak?
- 2. Legacy dekoratorlar
- 2.1 Yoqish va imzo
- 2.2 Ikki uslubni aralashtirib bo'lmaydi
- 2.3 Maydon, parametr va klass dekoratorlari
- 3. emitDecoratorMetadata — turlarni JavaScript'ga yozib qoldirish
- 3.1 tsc nimani qo'shadi
- 3.2 Faqat qiymati bor turlar yoziladi
- 3.3 reflect-metadata — o'qish uchun
- 4. Dependency injection
- 4.1 G'oya
- 4.2 Kichik konteyner
- 4.3 Jim buziladigan joy: import type
- 4.4 DI'ning asosiy foydasi — test
- 5. Bugungi kun: qaysi birini tanlash
- 5.1 Parametr xususiyatlari va erasableSyntaxOnly
- 5.2 Tanlov
- 6. Ko'p uchraydigan xatolar
- 6.1 TS1241 / TS1270 — uslublar aralashdi
- 6.2 TS1206 — parametr dekoratori bayroqsiz
- 6.3 Reflect.getMetadata is not a function
- 6.4 Bog'liqlik Object yoki Function bo'lib keldi
- 6.5 TS1294 — parametr xususiyati
- 7. Mashqlar
- 1-mashq (oson): Qaysi uslub?
- 2-mashq (o'rta): Metadata'ni bashorat qiling
- 3-mashq (qiyin): Amaliy tajriba — 40-di/
- 8. Real ishda
- Xulosa
- Manbalar
TypeScript legacy dekoratorlar: experimentalDecorators, reflect-metadata va dependency injection
Qisqacha: Legacy dekoratorlar — TypeScript'ning 2015-yildan beri bor, standartdan oldingi dekoratorlari. Ular
"experimentalDecorators": truebilan yoqiladi va boshqacha imzoga ega: metod dekoratori(target, key, descriptor)oladi, parametr dekoratorlari faqat shu rejimda bor."emitDecoratorMetadata": truebilantsckonstruktor parametrlarining turlarini JavaScript'ga yozib qo'yadi,reflect-metadatapaketi esa ularni o'qiydi. NestJS, TypeORM kabi freymvorklar aynan shu bilan dependency injection (bog'liqliklarni avtomatik berish) qiladi.
Bu darsda
- Legacy va standart dekoratorlarni imzosidan farqlaysiz va ularni aralashtirib bo'lmasligini bilasiz.
- Legacy metod, maydon, parametr va klass dekoratorlarini o'qiy va yoza olasiz.
emitDecoratorMetadatatscchiqishiga nima qo'shishini ko'rsatasiz.reflect-metadatabilan konstruktor parametrlarining turlarini o'qib, kichik DI konteyner yozasiz.- Bu mexanizm qayerda jim buzilishini (
interface,import type) bilasiz.
Oldin bilishingiz kerak: Standart dekoratorlar (TC39), Klasslar TypeScript'da, Modullar TypeScript'da, JS'da dizayn naqshlari.
1. Nega bu kerak?
O'tgan darsda standart dekoratorlarni o'rgandik. Endi Sardor internetda NestJS (Node server freymvorki, 24-qismdan keyin o'rganamiz — hozir bilish shart emas) misolini topdi:
@Injectable()
export class OrderService {
constructor(private readonly prices: PriceService) {}
}Bu yerda sirli narsa bor: OrderService ni hech kim new OrderService(new PriceService()) deb yaratmaydi. Freymvork o'zi "bu klassga PriceService kerak ekan" deb biladi va beradi. Qanday biladi? Turlar ishlash paytida o'chib ketadi-ku (Nega TypeScript darsidagi type erasure)!
Javob — bu darsning mavzusi. NestJS standart dekoratorlardan emas, TypeScript'ning eski dekoratorlaridan foydalanadi. Ular bitta qo'shimcha imkoniyat beradi: tsc parametr turlarini JavaScript'ga yozib qoldiradi. 2026-yil oktabr holatiga ko'ra NestJS'ning rasmiy boshlang'ich loyihasi (nestjs/typescript-starter) tsconfig.json ida:
"emitDecoratorMetadata": true,
"experimentalDecorators": true,va package.json da "reflect-metadata": "^0.2.2". Ish joyida bunday loyihaga albatta duch kelasiz. Kodni o'qiy olish uchun eski uslubni ham bilish kerak.
Hayotiy o'xshatish: restoran ta'minotchisi. Oshpaz "menga go'sht va guruch kerak" deb ro'yxat yozib qo'yadi, ta'minotchi esa ertalab hammasini o'zi olib keladi. Oshpaz bozorga bormaydi. DI konteyner — o'sha ta'minotchi, emitDecoratorMetadata — o'sha ro'yxat.
2. Legacy dekoratorlar
2.1 Yoqish va imzo
Legacy dekoratorlar tsconfig.json dagi bayroq bilan yoqiladi: "experimentalDecorators": true. Bayroq yoqilsa, hamma dekoratorlar eski qoidada ishlaydi — standart dekorator endi tanilmaydi.
Eski metod dekoratori uch argument oladi:
// @ts-options: {"experimentalDecorators": true}
function Log(
target: object,
key: string,
descriptor: PropertyDescriptor,
) {
const original = descriptor.value;
descriptor.value = function (...args: unknown[]) {
console.log(`-> ${key}(${args.join(", ")})`);
return original.apply(this, args);
};
}
class Kitchen {
@Log
cook(dish: string, count: number): string {
return `${count} ta ${dish}`;
}
}
console.log(new Kitchen().cook("Osh", 2));Konsolda:
-> cook(Osh, 2)
2 ta Oshtarget— klass prototipi (metod shu yerda yashaydi,classichidan darsidagi kabi).key— metod nomi.descriptor— xususiyat tavsifchisi:value(metodning o'zi),writable,enumerable(Xususiyat deskriptorlari). Dekorator uni joyida o'zgartiradi yoki yangisini qaytaradi.
Standartdagi (value, context) bilan solishtiring. Bu yerda descriptor.value — any: turlar zaif, original.apply ni tsc tekshirmaydi. Standart dekoratorda esa Method<This, Args, R> bilan aniq tur berish mumkin edi. Bu eski uslubning kamchiliklaridan biri.
2.2 Ikki uslubni aralashtirib bo'lmaydi
O'tgan darsdagi tur yozilgan logged ni experimentalDecorators yoqilgan loyihaga olib keldik:
// @ts-options: {"experimentalDecorators": true}
type Method<This, Args extends unknown[], R> =
(this: This, ...args: Args) => R;
function logged<This, Args extends unknown[], R>(
value: Method<This, Args, R>,
context: ClassMethodDecoratorContext<This, Method<This, Args, R>>,
): Method<This, Args, R> {
return value;
}
class Kitchen {
@logged
cook(): void {}
}Kompilyator:
TS1241: Unable to resolve signature of method decorator when called as an expression.
Argument of type 'Kitchen' is not assignable to parameter of type 'Method<unknown, unknown[], unknown>'.
Type 'Kitchen' provides no match for the signature '(this: unknown, ...args: unknown[]): unknown'.
TS1270: Decorator function return type 'Method<unknown, unknown[], unknown>' is not assignable to type 'void | TypedPropertyDescriptor<() => void>'.Pastdan o'qiymiz: "Kitchen da (this, ...args) => … imzosi yo'q" — ya'ni birinchi argument sifatida metod emas, prototip (Kitchen obyekti) keldi. Bu eski imzo: (target, key, descriptor). Ikkinchi xabar ham shuni aytadi: eski metod dekoratori void yoki TypedPropertyDescriptor qaytarishi kerak. Qoida: bitta loyihada — bitta uslub. Bayroq kodning hamma joyidagi @ ma'nosini o'zgartiradi.
| Standart (TS 5.0+) | Legacy (experimentalDecorators) |
|
|---|---|---|
| Metod imzosi | (value, context) |
(target, key, descriptor) |
| Parametr dekoratori | yo'q | bor |
| Tur metadatasi | yo'q | emitDecoratorMetadata |
| Kelajak | JavaScript standarti | faqat TypeScript |
2.3 Maydon, parametr va klass dekoratorlari
To'rt xil legacy dekoratorni bitta klassda ko'ramiz:
// @ts-options: {"experimentalDecorators": true}
function Field(target: object, key: string): void {
console.log("maydon:", key, "| target:", target.constructor.name);
}
function Param(target: object, key: string | undefined, i: number) {
console.log("parametr:", key ?? "constructor", i);
}
function Sealed(constructor: Function): void {
console.log("klass:", constructor.name);
Object.seal(constructor.prototype);
}
@Sealed
class Kitchen {
@Field chef = "Rustam aka";
constructor(@Param size: number) {}
cook(dish: string, @Param count: number): string {
return `${count} ta ${dish}`;
}
}
console.log(new Kitchen(3).cook("Osh", 2));Konsolda:
maydon: chef | target: Kitchen
parametr: cook 1
parametr: constructor 0
klass: Kitchen
2 ta Osh- Maydon dekoratori
(target, key)oladi — qiymat ham, deskriptor ham yo'q. U faqat "shu nomli maydon bor" deb belgi qo'ya oladi.target— prototip (target.constructor.name—Kitchen). - Parametr dekoratori
(target, key, index)— qaysi metodning nechanchi parametri. Konstruktordakey—undefined. U ham qiymatni o'zgartira olmaydi, faqat belgilaydi. NestJS'dagi@Body(),@Param("id")— shu tur. - Klass dekoratori konstruktor funksiyani oladi.
Sealedprototipni muhrladi (Object.seal) — endi unga yangi metod qo'shib bo'lmaydi. - Tartib: a'zolar (yozilish tartibida, parametr dekoratorlari o'z metodi bilan), keyin konstruktor parametrlari, oxirida klass.
Parametr dekoratori standartda yo'q. Uni experimentalDecorators siz yozib ko'ramiz:
function Inject(token: string) {
return (target: object, key: string | undefined, index: number) => {
console.log(`parametr ${index}: ${token}`);
};
}
class OrderService {
constructor(@Inject("PRICES") prices: unknown) {}
}Kompilyator:
TS1206: Decorators are not valid here.O'tgan darsdagi xabar: standart rejimda parametrga dekorator qo'yib bo'lmaydi. NestJS'ning @Inject(), @Body() lari standart dekoratorlarga o'tmasligining asosiy sabablaridan biri shu.
Tekshirib ko'ring: Eski loyihada
@Logmetod dekoratori(target, key, descriptor)deb yozilgan. Siztsconfig.jsondanexperimentalDecoratorsni olib tashladingiz. Nima bo'ladi?
Javob
@Log endi standart qoidada chaqiriladi: birinchi argument — metodning o'zi, ikkinchisi — context obyekti. Imzo mos kelmaydi va tsc TS1241 beradi. Turlarsiz JavaScript'da esa descriptor — undefined, descriptor.value qatori TypeError tashlardi. Ko'chish — har dekoratorni yangi imzoga qayta yozish demak.
3. emitDecoratorMetadata — turlarni JavaScript'ga yozib qoldirish
3.1 tsc nimani qo'shadi
emitDecoratorMetadata bayrog'i bilan tsc dekoratorli klass va a'zolar uchun ularning turlarini ham chiqishga yozadi. 40-di/src/bahor.ts dagi klass (mashqda to'liq yozamiz):
@Injectable()
export class OrderService {
readonly #prices: PriceList;
constructor(prices: PriceList) {
this.#prices = prices;
}
}tsc 7.0.2 chiqishi (dist/bahor.js, qisqartirilgan):
let OrderService = class OrderService {
#prices;
constructor(prices) {
this.#prices = prices;
}
};
OrderService = __decorate([
Injectable(),
__metadata("design:paramtypes", [PriceList])
], OrderService);Mana sir: __metadata("design:paramtypes", [PriceList]). tsc konstruktordagi prices: PriceList turini ko'rdi va klassning o'zini (qiymatni) massivga yozdi. Endi bu ma'lumot ishlash paytida ham bor. Turlar o'chdi, lekin ularning "izi" qoldi.
Uch xil kalit yoziladi:
| Kalit | Nima yoziladi |
|---|---|
design:paramtypes |
konstruktor (yoki metod) parametrlarining turlari |
design:type |
maydon yoki metodning turi |
design:returntype |
metod natijasining turi |
3.2 Faqat qiymati bor turlar yoziladi
Tur o'chadigan narsa bo'lsa, tsc nima yozadi? Sinab ko'rdik:
interface Prices { osh: number }
@Injectable()
class OrderService {
constructor(prices: Prices, count: number, name: string) {}
}Chiqishda:
__metadata("design:paramtypes", [Object, Number, String])number → Number, string → String (JavaScript'dagi o'ram konstruktorlari). Prices esa interface — ishlash paytida u yo'q, shuning uchun Object. Ya'ni interfeys bo'yicha bog'liqlikni avtomatik topib bo'lmaydi: Object hech narsa demaydi. Shuning uchun DI freymvorklarida "xizmat" har doim klass, interfeys emas. Interfeys bilan ishlash kerak bo'lsa, NestJS kabi freymvorklar qo'lda token beradi: @Inject("PRICES") — mana parametr dekoratorining asosiy ishi.
3.3 reflect-metadata — o'qish uchun
__metadata yordamchisi Reflect.metadata(…) ni chaqiradi. Lekin bunday funksiya JavaScript'da yo'q: Reflect obyektida faqat standart metodlar bor (Proxy va Reflect). Uni reflect-metadata paketi (0.2.2) qo'shadi — bu "polyfill": hali standart bo'lmagan imkoniyatni qo'lda yozilgan kod bilan to'ldiradi.
tsc chiqishidagi yordamchiga qarang:
var __metadata = function (k, v) {
if (typeof Reflect === "object" &&
typeof Reflect.metadata === "function")
return Reflect.metadata(k, v);
};Reflect.metadata yo'q bo'lsa — jim hech narsa qilmaydi. Xato chiqmaydi, metadata shunchaki yozilmaydi. Shuning uchun import "reflect-metadata"; qatori dasturning eng boshida, birinchi dekoratorli klass yuklanishidan oldin bo'lishi shart.
Turlar tomoni ham shu paketda. Usiz tsc getMetadata ni tanimaydi:
// @ts-options: {"experimentalDecorators": true}
function Injectable(): ClassDecorator {
return () => {};
}
@Injectable()
class OrderService {
constructor(name: string) {}
}
console.log(Reflect.getMetadata("design:paramtypes", OrderService));Kompilyator:
TS2339: Property 'getMetadata' does not exist on type 'typeof Reflect'.Tarjimasi: "typeof Reflect turida getMetadata xususiyati yo'q". import "reflect-metadata" qo'shilsa, paket Reflect turini declaration merging bilan kengaytiradi va xato yo'qoladi.
Tekshirib ko'ring:
import "reflect-metadata"unutilgan, lekintscboshqa joyda turlarni olgan (masalan, boshqa faylda import bor). Dastur ishga tushganda qayerda buziladi?
Javob
Ikki joyda, ikkalasi ham noxush. __metadata jim ishlamaydi — metadata yozilmaydi. Keyin konteyner Reflect.getMetadata(…) ni chaqirganda — TypeError: Reflect.getMetadata is not a function (biz 3-mashqda haqiqatda ko'ramiz). Xato metadata yozilgan joyda emas, o'qilgan joyda chiqadi — sababni topish qiyinroq.
4. Dependency injection
4.1 G'oya
Dependency injection (DI, "bog'liqlikni ichkariga berish") — klass o'ziga kerakli obyektlarni o'zi yaratmaydi, ularni tashqaridan oladi:
class PriceList {
priceOf(dish: string): number {
return dish === "osh" ? 35000 : 30000;
}
}
class OrderService {
readonly #prices: PriceList;
constructor(prices: PriceList) {
this.#prices = prices;
}
total(dishes: readonly string[]): number {
return dishes.reduce((s, d) => s + this.#prices.priceOf(d), 0);
}
}
const orders = new OrderService(new PriceList());
console.log(orders.total(["osh", "manti"])); // 65000Bu ham DI — qo'lda: new OrderService(new PriceList()). Foydasi: testda PriceList o'rniga soxtasini berish mumkin (testlarda tikuv — vazifalar dagi apiKlient({ fetch }) ham shu g'oya). Muammosi: 30 ta xizmatli serverda new A(new B(new C(), new D())) zanjiri juda uzun bo'ladi.
DI konteyner zanjirni o'zi quradi:
flowchart TB
A["resolve(OrderService)"] --> B["paramtypes:<br/>[PriceList]"]
B --> C["resolve(PriceList)"]
C --> D["new PriceList()"]
D --> E["new OrderService(priceList)"]Sxema bo'yicha: konteyner OrderService ning metadata'sini o'qiydi, unga PriceList kerakligini ko'radi, avval uni yaratadi (rekursiv, rekursiya kabi), keyin OrderService ni. Bitta resolve chaqiruvi — butun zanjir.
4.2 Kichik konteyner
20 qatorlik konteyner — NestJS yuragining soddalashtirilgan nusxasi. Blokning birinchi qismi — container.ts fayli. Oxirida esa uni ishlatib ko'ramiz: ikki xizmat @Injectable() bilan belgilangan (haqiqiy loyihada ular bahor.ts da turadi — mashqda to'liq yozamiz). Birinchi qatordagi ikki bayroq loyihada tsconfig.json da yoziladi:
// @ts-options: {"experimentalDecorators": true, "emitDecoratorMetadata": true}
// 40-di/src/container.ts — kichik DI konteyner (NestJS g'oyasi)
import "reflect-metadata";
type Class<T = unknown> = new (...args: never[]) => T;
const registered = new Set<Class>();
export function Injectable(): ClassDecorator {
return (target) => {
registered.add(target as unknown as Class);
};
}
export class Container {
readonly #instances = new Map<Class, unknown>();
resolve<T>(cls: Class<T>): T {
const ready = this.#instances.get(cls);
if (ready !== undefined) return ready as T;
if (!registered.has(cls)) {
throw new Error(`${cls.name} @Injectable() emas`);
}
const deps: Class[] =
Reflect.getMetadata("design:paramtypes", cls) ?? [];
const args = deps.map((dep) => this.resolve(dep));
const instance = new cls(...(args as never[]));
this.#instances.set(cls, instance);
return instance;
}
}
// --- Ishlatib ko'ramiz (loyihada — bahor.ts va main.ts) ---
@Injectable()
class PriceList {
priceOf(dish: string): number {
return dish === "osh" ? 35000 : 30000;
}
}
@Injectable()
class OrderService {
readonly #prices: PriceList;
constructor(prices: PriceList) {
this.#prices = prices;
}
total(dishes: readonly string[]): number {
return dishes.reduce((s, d) => s + this.#prices.priceOf(d), 0);
}
}
const orders = new Container().resolve(OrderService);
console.log(orders.total(["osh", "manti"])); // 65000Qatorma-qator:
Class<T>— "newbilan chaqiriladigan vaTyaratadigan narsa" turi.never[]argumentlar — "argumentlari qanday bo'lsa ham mos keladi".@Injectable()— fabrika (o'tgan darsdagi kabi qavsli). Klassni ro'yxatga oladi. Konteyner faqat belgilangan klasslarni yaratadi — tasodifiy klass emas.resolve— avval keshdan qidiradi: har klassdan bitta nusxa ("singleton", dizayn naqshlari). Keyin metadata'dan bog'liqliklarni oladi va har biri uchun o'zini chaqiradi.?? []— konstruktorsiz klassda (PriceList) metadata umuman yozilmaydi:getMetadataundefinedqaytaradi.- Uchta
as— type assertion. Ishlash paytidagi metadata'ning turinitscbilmaydi (getMetadataanyqaytaradi), shuning uchun konteyner ichida turni o'zimiz kafolatlaymiz. Bu — DI kutubxonalarining odatiy "chegarasi": ichida bir nechaas, tashqarida esaresolve(OrderService)aniqOrderServiceqaytaradi.
Endi blok oxiridagi foydalanishga qarang: new Container().resolve(OrderService) — va 65000. Hech qayerda new PriceList() yo'q — konteyner uni OrderService ning metadata'sidan o'zi topdi va yaratdi. Klasslar «G'oya» bo'limidagi qo'lda yozilgan variant bilan bir xil — farqi faqat @Injectable() qatorlarida.
4.3 Jim buziladigan joy: import type
Modullar darsida "faqat tur sifatida ishlatiladigan narsani import type bilan oling" dedik. DI'da bu qoida tuzoq:
import type { OrderService } from "./bahor.ts";
@Injectable()
export class Cashier {
readonly #orders: OrderService;
constructor(orders: OrderService) {
this.#orders = orders;
}
}tsc 7.0.2 — xatosiz (hatto verbatimModuleSyntax bilan ham). Chiqishda esa:
__metadata("design:paramtypes", [Function])OrderService emas — Function! import type qatori JavaScript'da o'chadi, tsc klassning qiymatiga murojaat qila olmaydi va "qandaydir funksiya" deb yozadi. Konteyner Function ni yaratishga urinadi va Function @Injectable() emas xatosini beradi — xato Cashier da emas, bog'liqlik o'qilganda. NestJS hujjati ham shuni ogohlantiradi: DI orqali olinadigan klass oddiy import bilan olinsin.
Qoida: dekoratorli klassning konstruktor parametrlaridagi klasslar — oddiy import. Bu emitDecoratorMetadata ning yagona joyi, bu yerda "tur" aslida qiymat sifatida ishlatiladi.
4.4 DI'ning asosiy foydasi — test
Konteyner faqat qulaylik emas. DI'ning haqiqiy foydasi — klassni boshqa bog'liqlik bilan sinash mumkinligi. OrderService PriceList ni o'zi yaratmagani uchun, testda unga soxta narxlar ro'yxatini beramiz. TypeScript bu yerda yordam beradi: soxta obyekt PriceList ning shakliga mos bo'lishi shart (Strukturaviy tiplash):
class PriceList {
priceOf(dish: string): number {
return dish === "osh" ? 35000 : 30000;
}
}
class OrderService {
readonly #prices: PriceList;
constructor(prices: PriceList) {
this.#prices = prices;
}
total(dishes: readonly string[]): number {
return dishes.reduce((s, d) => s + this.#prices.priceOf(d), 0);
}
}
// Testda: hamma taom 1000 so'm — hisoblash mantiqi alohida sinaladi
const fakePrices: PriceList = { priceOf: () => 1000 };
const orders = new OrderService(fakePrices);
console.log(orders.total(["osh", "manti", "choy"])); // 3000fakePrices — klassdan yaratilmagan oddiy obyekt. Lekin unda priceOf(dish): number bor, demak u PriceList turiga mos. Soxta narxlar bilan total ning faqat o'z ishi — yig'ish — tekshirildi. Haqiqiy narx o'zgarsa ham bu test buzilmaydi. priceOf ni noto'g'ri yozsangiz (masalan, satr qaytarsa), tsc soxta obyektni ham to'xtatadi.
Konteyner bo'lsa-yu, bu imkoniyat bo'lmasa — DI'dan foyda kam. NestJS'da ham testlar aynan shunday: Test.createTestingModule ga haqiqiy xizmat o'rniga soxtasi beriladi.
Tekshirib ko'ring:
OrderServicekonstruktoridathis.#prices = new PriceList()deb yozilganida, yuqoridagi testni yozib bo'larmidi?
Javob
Yo'q. Klass bog'liqligini o'zi yaratsa, tashqaridan boshqasini berishning yo'li yo'q — test doim haqiqiy PriceList bilan ishlaydi. Narxlar bazadan yoki serverdan kelsa, test sekin va beqaror bo'ladi. DI — "yaratma, so'ra" qoidasi: bog'liqlik konstruktor argumenti bo'lsa, uni almashtirish oson.
5. Bugungi kun: qaysi birini tanlash
5.1 Parametr xususiyatlari va erasableSyntaxOnly
NestJS misolidagi constructor(private readonly prices: PriceService) {} — klasslar darsidagi parametr xususiyati. Bizning kurs/mashqlar/15 va vazifalar da erasableSyntaxOnly yoqiq va u buni taqiqlaydi:
// @ts-options: {"erasableSyntaxOnly": true}
class PriceList {}
class OrderService {
constructor(private prices: PriceList) {}
}Kompilyator:
TS1294: This syntax is not allowed when 'erasableSyntaxOnly' is enabled.Tarjimasi: "erasableSyntaxOnly yoqilganda bu sintaksisga ruxsat yo'q". Parametr xususiyati konstruktorga this.prices = prices qatorini qo'shadi — o'chirib bo'lmaydi. Shuning uchun konteyner misolimizda readonly #prices va qo'lda yozilgan konstruktor bor. NestJS loyihasi esa tsc bilan yig'iladi va bu bayroqni yoqmaydi — u yerda parametr xususiyatlari odatiy.
5.2 Tanlov
- Yangi kod, freymvorksiz — standart dekoratorlar (o'tgan dars). Ular JavaScript standartiga aylanmoqda va turlari aniqroq.
- NestJS, TypeORM, Angular'ning eski versiyalari, InversifyJS, tsyringe — legacy: freymvork talab qiladi.
experimentalDecorators+emitDecoratorMetadata+reflect-metadata. - Aralashtirish — yo'q: bayroq butun loyihaga ta'sir qiladi.
- TypeScript 7.0 da ikkala uslub ham ishlaydi: TS 7.0 e'lonidagi olib tashlangan sozlamalar ro'yxatida
experimentalDecoratorsyo'q. Biz 7.0.2 da sinadik —__decorateva__metadatachiqishi o'sha-o'sha.
Node 24 legacy dekoratorni ham bajarmaydi (o'sha SyntaxError): har ikki uslub tsc yoki boshqa transpiler orqali o'tadi.
6. Ko'p uchraydigan xatolar
6.1 TS1241 / TS1270 — uslublar aralashdi
Internetdan standart dekorator olib, legacy loyihaga qo'ydingiz (yoki teskarisi). Tuzatish: loyihaning experimentalDecorators holatiga qarang va dekoratorni shu imzoga moslang.
6.2 TS1206 — parametr dekoratori bayroqsiz
Tuzatish: "experimentalDecorators": true — agar freymvork talab qilsa. Aks holda parametr dekoratori o'rniga konstruktor argumentini oddiy funksiya bilan tekshiring.
6.3 Reflect.getMetadata is not a function
import "reflect-metadata" yo'q yoki juda kech. Tuzatish: kirish faylining (main.ts) birinchi qatori.
6.4 Bog'liqlik Object yoki Function bo'lib keldi
Konstruktor parametri interface (→ Object) yoki import type bilan olingan klass (→ Function). Tuzatish: klass va oddiy import; interfeys uchun — token (@Inject("…")).
6.5 TS1294 — parametr xususiyati
erasableSyntaxOnly yoqilgan loyihada constructor(private x: X). Tuzatish: #x maydon + konstruktorda this.#x = x, yoki loyiha type stripping'siz yig'ilsa — bayroqni o'chirish (jamoa qarori).
7. Mashqlar
1-mashq (oson): Qaysi uslub?
Har imzoni uslubi bilan juftlang: (a) (value, context); (b) (target, key, descriptor); (c) (target, key, index). Standart uslubdagisi — [:a]. Faqat legacy'da bor parametr dekoratori — [:c].
Yechim
(a) — standart (TS 5.0+, context obyekti). (b) — legacy metod dekoratori. (c) — legacy parametr dekoratori: index — parametrning o'rni. Standartda parametr dekoratori yo'q, shuning uchun (c) faqat experimentalDecorators bilan ishlaydi.
2-mashq (o'rta): Metadata'ni bashorat qiling
emitDecoratorMetadata bilan tsc quyidagi konstruktor uchun design:paramtypes ga nima yozadi?
interface Menu { items: string[] }
class PriceList {}
@Injectable()
class Waiter {
constructor(menu: Menu, prices: PriceList, tables: number[]) {}
}Massivni to'ldiring: [Object, , ].
Yechim
__metadata("design:paramtypes", [Object, PriceList, Array])Menu — interfeys, ishlash paytida yo'q: Object. PriceList — klass, qiymati bor: o'zi yoziladi. number[] — massiv: Array (element turi yo'qoladi). Biz tsc 7.0.2 da tekshirdik. Demak konteyner Waiter uchun faqat PriceList ni topa oladi — Menu va number[] uchun token yoki qo'lda berish kerak.
3-mashq (qiyin): Amaliy tajriba — 40-di/
kurs/mashqlar/15/40-di/ — alohida build loyihasi (39-dekorator/ kabi; ildiz tsconfig.json ning exclude iga "40-di" ni qo'shing).
40-dida:package.json("type": "module"),npm install --save-exact reflect-metadata@0.2.2.tsconfig.json:strict,target: "es2024",module: "nodenext",rootDir: "src",outDir: "dist",types: ["node"],experimentalDecorators,emitDecoratorMetadata,rewriteRelativeImportExtensions.src/container.ts— darsdagi konteyner.src/bahor.ts—PriceList(priceOf(dish): osh 35 000, manti 30 000, boshqasi 0) vaOrderService(total(dishes)), ikkalasi@Injectable().test/container.test.js—../dist/…dan: zanjir o'zi yaratiladi (osh+manti= 65 000), bitta konteynerda bitta nusxa,@Injectable()siz klass — xato.- Ikki tajriba:
import "reflect-metadata"ni izohga oling; keyin uni qaytarib,import typebilanCashieryozing. Nima bo'ladi?
Yechim
src/bahor.ts:
// 40-di/src/bahor.ts — «Bahor» xizmatlari
import { Injectable } from "./container.ts";
@Injectable()
export class PriceList {
priceOf(dish: string): number {
const prices: Record<string, number> = {
osh: 35000,
manti: 30000,
};
return prices[dish] ?? 0;
}
}
@Injectable()
export class OrderService {
readonly #prices: PriceList;
constructor(prices: PriceList) {
this.#prices = prices;
}
total(dishes: readonly string[]): number {
return dishes.reduce((s, d) => s + this.#prices.priceOf(d), 0);
}
}test/container.test.js:
import { test } from "node:test";
import assert from "node:assert/strict";
import { Container } from "../dist/container.js";
import { OrderService, PriceList } from "../dist/bahor.js";
test("bog'liqlik o'zi yaratiladi", () => {
const orders = new Container().resolve(OrderService);
assert.equal(orders.total(["osh", "manti"]), 65000);
});
test("bitta konteynerda — bitta nusxa", () => {
const c = new Container();
assert.equal(c.resolve(PriceList), c.resolve(PriceList));
});
test("@Injectable() siz klass — xato", () => {
class Unknown {}
assert.throws(() => new Container().resolve(Unknown), /Injectable/);
});package.json skriptlari: "build": "tsc", "test": "npm run build && node --test test/container.test.js". npm test — bizda (Node 24.21.0, tsc 7.0.2) shunday chiqdi:
✔ bog'liqlik o'zi yaratiladi (3.3998ms)
✔ bitta konteynerda — bitta nusxa (0.4852ms)
✔ @Injectable() siz klass — xato (1.4769ms)
ℹ tests 3
ℹ suites 0
ℹ pass 3
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 367.69871-tajriba — import "reflect-metadata" izohda. tsc:
src/container.ts(24,15): error TS2339: Property 'getMetadata' does not exist on type 'typeof Reflect'.Xato bo'lsa ham tsc dist/ ni yozdi (noEmitOnError yo'q — o'rnatish darsidagi tuzoq). Uni ishga tushirsak: TypeError: Reflect.getMetadata is not a function.
2-tajriba — import type { OrderService } bilan Cashier. tsc jim, chiqishda __metadata("design:paramtypes", [Function]), resolve(Cashier) esa Error: Function @Injectable() emas. Oddiy import { OrderService } bilan — [OrderService] va hammasi ishlaydi.
XATOLAR.md ga TS1206 (parametr dekoratori), TS2339 (getMetadata), TS1294 va ikki ishlash xatosini qo'shing. dist/ va node_modules/ — .gitignore da:
git add 15/40-di 15/tsconfig.json 15/XATOLAR.md
git commit -m "15/40: kichik DI konteyner, reflect-metadata, testlar"8. Real ishda
- NestJS (Node server, 24-qismdan keyingi qismlarda):
@Module,@Controller,@Injectable— hammasi legacy dekoratorlar va DI. Bugungi konteyner uning eng kichik modeli. - TypeORM (ma'lumotlar bazasi bilan ishlash kutubxonasi):
@Entity(),@Column()— maydon dekoratorlari vadesign:typemetadata'si. class-validator:@IsString(),@MinLength(3)— NestJS formalarni shu bilan tekshiradi. - Angular ham dekoratorlarga qurilgan, lekin o'z kompilyatori bor va
reflect-metadataga tayanmaydi. - Intervyu: "Dependency injection nima va nega kerak?", "NestJS klass turini qanday biladi?", "
emitDecoratorMetadatanima yozadi?", "Interfeysni nega inject qilib bo'lmaydi?".
Xulosa
- Legacy dekoratorlar —
experimentalDecorators: metod(target, key, descriptor), maydon(target, key), parametr(target, key, index), klass(constructor). Standart bilan aralashtirib bo'lmaydi. - Parametr dekoratorlari va tur metadatasi — faqat legacy'da. Shuning uchun NestJS, TypeORM hali shu uslubda.
emitDecoratorMetadata—__metadata("design:paramtypes", [PriceList]): tur o'chadi, lekin klass qiymati yoziladi.interface→Object,import type→Function.reflect-metadata—Reflect.getMetadatapolyfill'i va turlari; kirish faylining birinchi qatorida import qilinadi.- DI konteyner metadata'ni o'qib, bog'liqliklar zanjirini o'zi quradi —
new A(new B(…))o'rnigaresolve(A).
Keyingi dars: TypeScript va DOM — querySelector qaytaradigan Element | null, HTMLInputElement va hodisa turlari; vazifalar ning DOM modullari .ts ga o'tadi.
Manbalar
- TypeScript Handbook: "Decorators" (experimentalDecorators) — typescriptlang.org/docs/handbook/decorators.html
- TSConfig:
experimentalDecorators,emitDecoratorMetadata— typescriptlang.org/tsconfig - reflect-metadata — github.com/microsoft/reflect-metadata (npm 0.2.2)
- NestJS hujjatlari: "Providers", "Custom providers" — docs.nestjs.com; boshlang'ich loyiha — github.com/nestjs/typescript-starter
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!