TypeScript'da shartli va mapped tiplar nima?

SeniorTypeScript #typescript #advanced

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 infer funksiyaning oddiy generic-parametridan nimasi bilan farq qiladi?
  • Ichki obyektlarni rekursiv ravishda ixtiyoriy qiladigan DeepPartial<T> utility-tipini qanday amalga oshirish mumkin?