TypeScript / Generic үндэс

Generic үндэс

Кодоо бичихдээ "энэ функц зөвхөн тоонуудад биш, дурын төрлийн өгөгдөлд ажиллана" гэх мэт нөхцөл байдал байнга тохиолддог. TypeScript-ийн generic нь яг энэ асуудлыг шийддэг — нэг удаа бичиж, олон өөр төрлийн өгөгдөлд дахин дахин ашиглаж болох кодыг бичих арга юм.

Generic гэж юу вэ?

Generic-гүйгээр нэг функцийг олон төрлөөр бичвэл хэрхэн давтагдаж байдгийг үзье:

typescript
// Давтамжтай — төрөл тус бүрд тусдаа функц
function анхны_тоо(массив: number[]): number {
  return массив[0];
}

function анхны_мөр(массив: string[]): string {
  return массив[0];
}

function анхны_логик(массив: boolean[]): boolean {
  return массив[0];
}

// Generic ашиглавал нэг функц хангалттай
function анхны<T>(массив: T[]): T {
  return массив[0];
}

console.log(анхны([1, 2, 3])); // 1 — T = number
console.log(анхны(["а", "б", "в"])); // "а" — T = string
console.log(анхны([true, false, true])); // true — T = boolean

T бол төрлийн параметр (type parameter). Ямар ч үсэг хэрэглэж болох ч T (Type-ийн товчлол) заншил болсон байдаг.

T гэдэг юуг илэрхийлэх вэ?

T нь функцийг дуудах үед TypeScript автоматаар тодорхойлдог "хоосон зай" юм:

typescript
function боох<T>(утга: T): { өгөгдөл: T; огноо: string } {
  return {
    өгөгдөл: утга,
    огноо: new Date().toISOString(),
  };
}

const боогдсон_тоо = боох(42);
// { өгөгдөл: number, огноо: string }

const боогдсон_мөр = боох("Монгол");
// { өгөгдөл: string, огноо: string }

// Төрлийг гараар зааж болно
const боогдсон = боох<boolean>(true);

TypeScript параметрийн утгаас Tдүгнэн гаргадаг (inference). боох(42) гэхэд 42 нь number тул T = number гэж автоматаар ойлгодог.

Олон төрлийн параметр

Нэгээс олон төрлийн параметр хэрэглэж болно:

typescript
// T ба U хоёр өөр төрлийн параметр
function хослуул<T, U>(эхний: T, хоёрдугаар: U): [T, U] {
  return [эхний, хоёрдугаар];
}

const хос1 = хослуул("нэр", 25); // [string, number]
const хос2 = хослуул(true, ["a", "b"]); // [boolean, string[]]

// Key-Value хослол
function объект_үүсгэх<K extends string, V>(түлхүүр: K, утга: V): Record<K, V> {
  return { [түлхүүр]: утга } as Record<K, V>;
}

const объект = объект_үүсгэх("нэр", "Болд");
// { нэр: "Болд" }

Generic interface

interface болон type-д мөн generic ашиглаж болно:

typescript
// Generic interface
interface Хариу<T> {
  өгөгдөл: T;
  амжилттай: boolean;
  алдаа?: string;
}

// Дурын төрлийн өгөгдлийг боож буцаах
const хэрэглэгчийн_хариу: Хариу<{ нэр: string; нас: number }> = {
  өгөгдөл: { нэр: "Болд", нас: 25 },
  амжилттай: true,
};

const алдааны_хариу: Хариу<null> = {
  өгөгдөл: null,
  амжилттай: false,
  алдаа: "Сервертэй холбогдож чадсангүй",
};

Generic-тэй болсноор Хариу interface нэг удаа бичсэн боловч хэрэглэгч, бүтээгдэхүүн, захиалга — дурын төрлийн өгөгдлийг боох боломжтой болно.

Яагаад generic хэрэгтэй вэ?

Generic хэрэглэхгүйгээр мөн any ашиглаж болох юм шиг санагдаж болно. Гэвч тэдгээр хоёрын хооронд чухал ялгаа бий:

typescript
// any ашиглавал — бүх мэдээлэл алдагдана
function анхны_any(массив: any[]): any {
  return массив[0];
}

const утга = анхны_any([1, 2, 3]);
утга.toUpperCase(); // Алдааг ажиллах үед л олно!

// Generic ашиглавал — төрлийн мэдээлэл хадгалагдана
function анхны<T>(массив: T[]): T {
  return массив[0];
}

const тоо = анхны([1, 2, 3]);
тоо.toUpperCase(); // Алдаа: number дээр toUpperCase байхгүй ✅

Generic нь any-ийн уян хатан байдлыг хадгалж, TypeScript-ийн төрлийн шалгалтыг алдагдуулахгүй.

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

Generic функц — extends ашиглан төрлийн параметрт хязгаарлалт тавих, keyof оператор хэрхэн ажилладагыг сурна.