TypeScript / infer түлхүүр үг

infer түлхүүр үг

infer нь conditional type-ийн дотор ашиглагдах онцгой түлхүүр үг юм. "Энэ байранд байгаа төрлийг TypeScript өөрөө дүгнэн гарга, би нэр өгнө" гэсэн утгатай. Эхлээд ойлгоход хэцүү мэт боловч дадаад ирвэл маш хэрэгтэй хэрэглүүр.

infer гэж яагаад хэрэгтэй вэ?

Promise<string> дотроос string-г авах гэж оролдоё:

typescript
// infer ашиглахгүйгээр — боломжгүй
type PromiseДотор<T> = T extends Promise<???> ? ??? : never;
// ???-д юу бичих вэ? T-д юу байгааг бид мэдэхгүй!

// infer ашиглавал — TypeScript өөрөө дүгнэнэ
type PromiseДотор<T> = T extends Promise<infer R> ? R : never;

type Тест1 = PromiseДотор<Promise<string>>;  // string
type Тест2 = PromiseДотор<Promise<number>>;  // number
type Тест3 = PromineДотор<Promise<{ нэр: string }>>; // { нэр: string }
type Тест4 = PromiseДотор<boolean>; // never — Promise биш

infer R нь "энэ байрандаа байгаа төрлийг R гэж нэрлэ" гэсэн утгатай. TypeScript тохирох тохиолдолд R-д яг зөв төрлийг тавина.

Функцийн параметр ба буцаах төрлийг авах

typescript
// Функцийн буцаах төрлийг авах
type БуцаахТөрөл<T> = T extends (...args: any[]) => infer R ? R : never;

// Функцийн эхний параметрийн төрлийг авах
type ЭхнийПараметр<T> = T extends (эхний: infer P, ...args: any[]) => any
  ? P
  : never;

// Бүх параметрийг tuple болгон авах
type БүхПараметр<T> = T extends (...args: infer P) => any ? P : never;

function хадгалах(нэр: string, нас: number, хот: string): boolean {
  return true;
}

type Буцаалт = БуцаахТөрөл<typeof хадгалах>; // boolean
type Эхний = ЭхнийПараметр<typeof хадгалах>; // string
type БүхПарам = БүхПараметр<typeof хадгалах>; // [string, number, string]

TypeScript-ийн суурилсан ReturnType<T> ба Parameters<T> utility types яг ийм аргаар бичигдсэн.

Массив ба tuple-ийн элемент авах

typescript
// Массивын элементийн төрлийг авах
type МассивЭлемент<T> = T extends (infer E)[] ? E : never;

type Тест1 = МассивЭлемент<string[]>; // string
type Тест2 = МассивЭлемент<number[]>; // number
type Тест3 = МассивЭлемент<boolean[]>; // boolean

// Tuple-ийн эхний ба сүүлийн элемент
type ТuplеЭхний<T extends any[]> = T extends [infer Эхний, ...any[]]
  ? Эхний
  : never;
type ТuplеСүүлийн<T extends any[]> = T extends [...any[], infer Сүүлийн]
  ? Сүүлийн
  : never;

type Х1 = ТuplеЭхний<[string, number, boolean]>; // string
type Х2 = ТuplеСүүлийн<[string, number, boolean]>; // boolean
type Х3 = ТuplеЭхний<[42, "Монгол"]>; // number (42)

Nested promise задлах

typescript
// Давхарласан Promise-г бүрэн задлах
type ГүнПромис<T> = T extends Promise<infer R> ? ГүнПромис<R> : T;

type Тест1 = ГүнПромис<Promise<string>>; // string
type Тест2 = ГүнПромис<Promise<Promise<number>>>; // number
type Тест3 = ГүнПромис<Promise<Promise<Promise<boolean>>>>; // boolean
type Тест4 = ГүнПромис<string>; // string — Promise биш

ГүнПромис өөрийгөө дуудаж байна — энийг рекурсив conditional type гэдэг. TypeScript 4.1-с дэмжигддэг.

Бодит жишээ: хэлбэрийн өгөгдлийг задлах

typescript
// API хариуг задлах
interface АПИХариу<T> {
  өгөгдөл: T;
  амжилттай: boolean;
}

type АПИӨгөгдөл<T> = T extends АПИХариу<infer D> ? D : never;

type Хэрэглэгч = { нэр: string; нас: number };
type ХэрэглэгчХариу = АПИХариу<Хэрэглэгч>;

type ЗадлагдсанХэрэглэгч = АПИӨгөгдөл<ХэрэглэгчХариу>;
// { нэр: string; нас: number }

// Хэрэглэгч ба ХэрэглэгчХариу-г тусад нь бичихгүйгээр шууд задлаж болно
async function апи_авах<T>(url: string): Promise<T> {
  const хариу = await fetch(url);
  return хариу.json();
}

type АвахБуцаалт = АПИӨгөгдөл<АПИХариу<Хэрэглэгч>>; // { нэр: string; нас: number }

infer нь conditional type-ийн хамгийн хүчирхэг хэсэг. Utility types-ийн дотоод хэрэгжилтийг ойлгох, өөрийн гэсэн нарийн type helper бичих үед зайлшгүй хэрэгтэй болдог.

Дараагийн хичээлд:

Модулиуд ба namespace — TypeScript код файлуудад хэрхэн хуваах, import/export зөв ашиглах, namespace-ийн үүргийг сурна.