TypeScript'da shartli va mapped tiplar nima?
Savol
Shartli tiplar (conditional types) va mapped tiplar (mapped types) qanday ishlaydi va ular nima uchun kerak?
Qisqa javob
Shartli tip T extends U ? X : Y T Uga mos kelish-kelmasligiga qarab ikkita tipdan birini tanlaydi — bu tiplar darajasidagi shartli tarmoqlanish. Mapped tip { [K in keyof T]: ... } mavjud tipning kalitlarini sanab o'tib va har biriga transformatsiya qo'llab, yangi obyekt tipini quradi.
Batafsil javob
Shartli tiplar kompilyator tiplarni tahlil qilish bosqichida hisoblaydigan "agar tip shartni qanoatlantirsa — bir natija, aks holda — boshqasi" mantig'ini ifodalash imkonini beradi:
type IsString<T> = T extends string ? true : false
type A = IsString<'hello'> // true
type B = IsString<42> // false
Shartli tiplarning asosiy imkoniyati — infer operatori, u shart ichida tipning bir qismini "ajratib olish" imkonini beradi:
type ElementType<T> = T extends (infer U)[] ? U : T
type A = ElementType<string[]> // string
type B = ElementType<number> // number (T massiv emas, else tarmog'i)
type ReturnOf<F> = F extends (...args: any[]) => infer R ? R : never
type R1 = ReturnOf<() => string> // string
T union tip bo'lganda, shartli tip avtomatik ravishda distributiv bo'ladi: union'ning har bir a'zosiga alohida-alohida qo'llaniladi, natijalar esa qayta union'ga birlashtiriladi:
type ToArray<T> = T extends any ? T[] : never
type Result = ToArray<string | number> // string[] | number[], (string | number)[] emas
Mapped tiplar keyof Tning barcha kalitlarini sanab o'tadi va har bir xususiyatga modifikator qo'llab, yangi tip quradi. Bu ko'plab o'rnatilgan utility-tiplarning (Partial, Readonly, Pick aynan mapped types orqali amalga oshirilgan) asosidir:
type MyPartial<T> = { [K in keyof T]?: T[K] }
type MyReadonly<T> = { readonly [K in keyof T]: T[K] }
Mapped type ichida as yordamida kalitlarni qayta nomlash (key remapping) mumkin — masalan, prefiks qo'shish yoki chiqarib tashlanadigan kalitlar uchun never qaytaradigan shartli tip orqali xususiyatlarni filtrlash:
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]
}
interface Person { name: string; age: number }
type PersonGetters = Getters<Person>
// { getName: () => string; getAge: () => number }
Misol
// discriminated union'dan muvaffaqiyatli natija tipini ajratib olish
type ExtractSuccess<T> = T extends { status: 'success'; data: infer D } ? D : never
type ApiResult =
| { status: 'success'; data: { id: number } }
| { status: 'error'; message: string }
type SuccessData = ExtractSuccess<ApiResult> // { id: number }
// mapped type: obyektning barcha xususiyatlarini nullable qilamiz
type Nullable<T> = { [K in keyof T]: T[K] | null }
interface Form { email: string; age: number }
type NullableForm = Nullable<Form>
// { email: string | null; age: number | null }
// conditional + mapped kombinatsiyasi: barcha metodlarni olib tashlash (faqat ma'lumotlarni qoldirish)
type NonFunctionKeys<T> = {
[K in keyof T]: T[K] extends (...args: any[]) => any ? never : K
}[keyof T]
type DataOnly<T> = Pick<T, NonFunctionKeys<T>>
class User {
name = 'Alice'
age = 30
greet() { return `Hi, ${this.name}` }
}
type UserData = DataOnly<User> // { name: string; age: number }
Qo'shimcha savollar
- Shartli tiplarning distributivligi qanday ishlaydi va uni qanday o'chirish mumkin (
Tni[T]ga o'rash orqali)? - Shartli tiplardagi
inferfunksiyaning oddiy generic-parametridan nimasi bilan farq qiladi? - Ichki obyektlarni rekursiv ravishda ixtiyoriy qiladigan
DeepPartial<T>utility-tipini qanday amalga oshirish mumkin?