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

Что такое Generic type?

1.7 Middle🔥 171 комментариев
#TypeScript

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

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

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

# Generic Type (Обобщённые типы)

Generic Type — это способ писать код, который может работать с разными типами данных, сохраняя типобезопасность. Это один из самых мощных инструментов в TypeScript для создания переиспользуемого и типизированного кода.

Базовая концепция

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

// Без Generics — теряем типизацию
function getFirstElement(array: any[]): any {
  return array[0];
}

const result = getFirstElement([1, 2, 3]); // type: any

// С Generics — сохраняем типизацию
function getFirstElement<T>(array: T[]): T {
  return array[0];
}

const numResult = getFirstElement([1, 2, 3]);  // type: number
const strResult = getFirstElement(['a', 'b']); // type: string

Здесь <T> это Generic параметр, который обозначает "какой-то тип". TypeScript автоматически определяет T на основе переданных аргументов.

Синтаксис Generics

// Функция с одним Generic параметром
function identity<T>(value: T): T {
  return value;
}

// Функция с несколькими Generic параметрами
function pair<T, U>(first: T, second: U): [T, U] {
  return [first, second];
}

// Применение
const result1 = identity<string>('hello');      // Явное указание типа
const result2 = identity('hello');              // Автоматическое определение
const tuple = pair<number, string>(42, 'life'); // Несколько типов

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

Пример 1: Generic интерфейс для данных

// Generic интерфейс для ответа API
interface ApiResponse<T> {
  status: number;
  data: T;
  message: string;
}

// Конкретные типы на основе Generic
interface User {
  id: number;
  name: string;
  email: string;
}

interface Post {
  id: number;
  title: string;
  content: string;
}

// Использование Generic интерфейса
const userResponse: ApiResponse<User> = {
  status: 200,
  data: { id: 1, name: 'John', email: 'john@example.com' },
  message: 'Success',
};

const postResponse: ApiResponse<Post> = {
  status: 200,
  data: { id: 1, title: 'Hello', content: 'World' },
  message: 'Success',
};

// Автоматическая типизация
const userName: string = userResponse.data.name; // OK
const postTitle: string = postResponse.data.title; // OK

Пример 2: Generic функция для API запросов

// Generic функция для fetch запросов
async function fetchData<T>(url: string): Promise<T> {
  const response = await fetch(url);
  if (!response.ok) {
    throw new Error(`HTTP ${response.status}`);
  }
  return response.json() as T;
}

// Использование с конкретными типами
interface GitHubUser {
  id: number;
  login: string;
  avatar_url: string;
}

const user = await fetchData<GitHubUser>(
  'https://api.github.com/users/octocat'
);

// Type автоматически GitHubUser
console.log(user.login); // OK
console.log(user.unknown); // ❌ Error: Property 'unknown' does not exist

Пример 3: Generic класс для хранилища

class Storage<T> {
  private items: T[] = [];
  
  add(item: T): void {
    this.items.push(item);
  }
  
  get(index: number): T | undefined {
    return this.items[index];
  }
  
  getAll(): T[] {
    return this.items;
  }
  
  remove(item: T): void {
    const index = this.items.indexOf(item);
    if (index > -1) {
      this.items.splice(index, 1);
    }
  }
}

// Использование с разными типами
const numberStorage = new Storage<number>();
numberStorage.add(1);
numberStorage.add(2);
console.log(numberStorage.get(0)); // type: number | undefined

const stringStorage = new Storage<string>();
stringStorage.add('hello');
stringStorage.add('world');
console.log(stringStorage.get(0)); // type: string | undefined

Ограничения Generic (Constraints)

Иногда нужно ограничить какие типы можно использовать:

// Generic с ограничением — T должен быть объектом с свойством length
function getLength<T extends { length: number }>(value: T): number {
  return value.length;
}

getLength('hello');        // OK — строка имеет length
getLength([1, 2, 3]);      // OK — массив имеет length
getLength({ length: 5 });  // OK — объект имеет length
getLength(42);             // ❌ Error — число не имеет length

// Generic с ограничением наследованием
interface HasName {
  name: string;
}

function getName<T extends HasName>(obj: T): string {
  return obj.name;
}

getName({ name: 'John', age: 30 }); // OK
getName({ age: 30 });               // ❌ Error — нет свойства name

Generic в React компонентах

// Generic компонент для списка
interface ListProps<T> {
  items: T[];
  renderItem: (item: T, index: number) => React.ReactNode;
  onItemClick: (item: T) => void;
}

function List<T extends { id: string | number }>(
  { items, renderItem, onItemClick }: ListProps<T>
) {
  return (
    <ul>
      {items.map((item, index) => (
        <li
          key={item.id}
          onClick={() => onItemClick(item)}
        >
          {renderItem(item, index)}
        </li>
      ))}
    </ul>
  );
}

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

const users: User[] = [
  { id: 1, name: 'John', email: 'john@example.com' },
  { id: 2, name: 'Jane', email: 'jane@example.com' },
];

function UsersList() {
  return (
    <List<User>
      items={users}
      renderItem={(user) => `${user.name} (${user.email})`}
      onItemClick={(user) => console.log('Selected:', user.id)}
    />
  );
}

Generic хуки

// Generic хук для асинхронных операций
interface UseAsyncState<T> {
  loading: boolean;
  data: T | null;
  error: Error | null;
}

function useAsync<T>(
  asyncFunction: () => Promise<T>,
  immediate = true
): UseAsyncState<T> & { execute: () => Promise<T> } {
  const [state, setState] = React.useState<UseAsyncState<T>>({
    loading: immediate,
    data: null,
    error: null,
  });
  
  const execute = React.useCallback(async () => {
    setState({ loading: true, data: null, error: null });
    try {
      const response = await asyncFunction();
      setState({ loading: false, data: response, error: null });
      return response;
    } catch (error) {
      setState({ loading: false, data: null, error: error as Error });
      throw error;
    }
  }, [asyncFunction]);
  
  React.useEffect(() => {
    if (immediate) {
      execute();
    }
  }, [execute, immediate]);
  
  return { ...state, execute };
}

// Использование Generic хука
function UserProfile({ userId }: { userId: number }) {
  const { loading, data: user, error } = useAsync(
    () => fetch(`/api/users/${userId}`).then(r => r.json()) as Promise<User>
  );
  
  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;
  if (!user) return null;
  
  return <div>{user.name}</div>; // user type: User
}

Продвинутые техники

Conditional Types

// Conditional type в зависимости от типа
type IsString<T> = T extends string ? true : false;

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

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

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

Keyof constraint

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

const user: User = { id: 1, name: 'John', email: 'john@example.com' };

getProperty(user, 'name');   // OK — 'name' это ключ User
getProperty(user, 'age');    // ❌ Error — 'age' не ключ User

Частые ошибки

Ошибка 1: Забыли указать Generic

// ❌ ПЛОХО
const response: ApiResponse = { /* ... */ }; // type: ApiResponse<unknown>

// ✅ ХОРОШО
const response: ApiResponse<User> = { /* ... */ }; // type: ApiResponse<User>

Ошибка 2: Неправильное ограничение

// ❌ ПЛОХО: Generic не имеет смысла с этим ограничением
function process<T extends User>(item: T): string {
  return item.name;
}

// Лучше просто использовать User
function process(item: User): string {
  return item.name;
}

Вывод

  1. Generics позволяют писать переиспользуемый код сохраняя типизацию
  2. Используй <T> для параметра типа и TypeScript автоматически определит его
  3. Ограничения (extends) помогают сузить возможные типы
  4. Generic интерфейсы и функции улучшают типизацию API
  5. В React компонентах Generics делают их переиспользуемыми
  6. Профайли вместе с коллегами чтобы убедиться что тип правильный
Что такое Generic type? | PrepBro