← Назад к вопросам

Что такое стандартный Generic?

2.0 Middle🔥 201 комментариев
#TypeScript

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Что такое стандартный Generic?

Generic (обобщение, шаблон) в TypeScript — это способ создания компонентов (функций, классов, интерфейсов), которые работают с множеством типов данных, сохраняя при этом информацию о типе. Это основной механизм обеспечения типобезопасности при работе с переиспользуемым кодом.

Основная идея

Generic позволяет писать код, который работает с различными типами данных, но при этом сохраняет информацию о конкретном используемом типе.

// Без Generic — теряем информацию о типе
function identity(value: any): any {
  return value;
}

const result = identity(42);
// результат имеет тип any, мы не знаем, что это число

// С Generic — сохраняем информацию о типе
function identity<T>(value: T): T {
  return value;
}

const result = identity(42);
// result имеет тип number
const text = identity('hello');
// text имеет тип string

Синтаксис Generic

T — это переменная типа, которая будет заменена на конкретный тип при использовании:

// T — типовая переменная
function getValue<T>(value: T): T {
  return value;
}

// Явное указание типа
const num = getValue<number>(42);

// Неявный вывод типа (type inference)
const str = getValue('hello'); // T выводится как string

Generic в функциях

// Простая функция с одним типовым параметром
function getFirst<T>(array: T[]): T {
  return array[0];
}

const firstNumber = getFirst([1, 2, 3]);     // T = number
const firstString = getFirst(['a', 'b']);    // T = string

// Несколько типовых параметров
function pair<T, U>(first: T, second: U): [T, U] {
  return [first, second];
}

const p = pair('hello', 42);  // T = string, U = number
const result: [string, number] = p;

// Функция с ограничением типа
function getLength<T extends { length: number }>(value: T): number {
  return value.length;
}

getLength('hello');        // ✅ строки имеют length
getLength([1, 2, 3]);      // ✅ массивы имеют length
getLength(42);             // ❌ ошибка: number не имеет length

Generic в интерфейсах и типах

// Generic интерфейс
interface Container<T> {
  value: T;
  getValue(): T;
  setValue(value: T): void;
}

// Реализация Generic интерфейса
class Box<T> implements Container<T> {
  private value: T;

  constructor(value: T) {
    this.value = value;
  }

  getValue(): T {
    return this.value;
  }

  setValue(value: T): void {
    this.value = value;
  }
}

// Использование
const numberBox = new Box<number>(42);
const stringBox = new Box<string>('hello');

// Generic тип
type Response<T> = {
  data: T;
  status: number;
  message: string;
};

const userResponse: Response<{ id: number; name: string }> = {
  data: { id: 1, name: 'John' },
  status: 200,
  message: 'Success',
};

Generic в классах

// Generic класс
class Stack<T> {
  private items: T[] = [];

  push(item: T): void {
    this.items.push(item);
  }

  pop(): T | undefined {
    return this.items.pop();
  }

  peek(): T | undefined {
    return this.items[this.items.length - 1];
  }

  isEmpty(): boolean {
    return this.items.length === 0;
  }
}

// Использование
const numberStack = new Stack<number>();
numberStack.push(1);
numberStack.push(2);
const top = numberStack.pop(); // тип: number | undefined

const stringStack = new Stack<string>();
stringStack.push('hello');
const word = stringStack.pop(); // тип: string | undefined

Ограничения типов (Constraints)

// Ограничение: T должен быть объектом с определёнными свойствами
function printName<T extends { name: string }>(obj: T): void {
  console.log(obj.name);
}

printName({ name: 'John', age: 30 }); // ✅
printName({ age: 30 }); // ❌ ошибка: нет свойства name

// Ограничение: T должен быть расширением определённого типа
function merge<T extends object, U extends object>(obj1: T, obj2: U): T & U {
  return { ...obj1, ...obj2 };
}

const merged = merge({ a: 1 }, { b: 2 });
// merged имеет тип { a: number } & { b: number }

// Ограничение: T должен быть ключом другого типа
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

const obj = { name: 'John', age: 30 };
const name = getProperty(obj, 'name'); // ✅ 'name' существует
getProperty(obj, 'email'); // ❌ ошибка: 'email' не в объекте

Практические примеры

Пример 1: API запрос с Generic

interface ApiResponse<T> {
  data: T;
  status: number;
  message: string;
}

async function fetchData<T>(url: string): Promise<ApiResponse<T>> {
  const response = await fetch(url);
  return response.json();
}

// Использование
interface User {
  id: number;
  name: string;
  email: string;
}

const response = await fetchData<User>('/api/user/1');
// response.data имеет тип User
console.log(response.data.name);

Пример 2: Generic утилита для фильтрации

function filterByProperty<T, K extends keyof T>(
  items: T[],
  property: K,
  value: T[K]
): T[] {
  return items.filter(item => item[property] === value);
}

const users = [
  { id: 1, name: 'John', active: true },
  { id: 2, name: 'Jane', active: false },
  { id: 3, name: 'Bob', active: true },
];

const activeUsers = filterByProperty(users, 'active', true);
// activeUsers содержит только пользователей с active: true

Пример 3: Generic хук React

function useFetch<T>(url: string) {
  const [data, setData] = useState<T | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<Error | null>(null);

  useEffect(() => {
    fetch(url)
      .then(res => res.json())
      .then((data: T) => setData(data))
      .catch(err => setError(err))
      .finally(() => setLoading(false));
  }, [url]);

  return { data, loading, error };
}

// Использование
interface Post {
  id: number;
  title: string;
  body: string;
}

const { data: posts, loading } = useFetch<Post[]>('/api/posts');
// posts имеет тип Post[] | null

Default типы для Generic

interface Container<T = string> {
  value: T;
}

const container1: Container = { value: 'hello' }; // T = string по умолчанию
const container2: Container<number> = { value: 42 }; // T явно указан

Условные типы (Conditional Types)

// Условный тип в зависимости от значения T
type IsString<T> = T extends string ? true : false;

type A = IsString<'hello'>;  // true
type B = IsString<number>;   // false

// Практический пример
type Flatten<T> = T extends Array<infer U> ? U : T;

type Str = Flatten<string[]>;    // string
type Num = Flatten<number>;      // number

Лучшие практики

// ✅ ПРАВИЛЬНО
function identity<T>(value: T): T {
  return value;
}

// ✅ Используй ограничения типов
function merge<T extends object, U extends object>(a: T, b: U): T & U {
  return { ...a, ...b };
}

// ❌ НЕПРАВИЛЬНО — слишком общий Generic
function process<T>(data: T): any {
  return data; // Теряется типизация
}

// ✅ ПРАВИЛЬНО — специфичный Generic
function process<T extends object>(data: T): T {
  return data; // Сохраняется типизация
}

Вывод: Generic — это фундаментальный инструмент TypeScript, обеспечивающий типобезопасность и переиспользуемость кода. Правильное использование Generic делает код более надёжным и удобнее в использовании.