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

В чем разница между T Extends и T =?

2.3 Middle🔥 121 комментариев
#TypeScript

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

🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)

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

В чем разница между T Extends и T =?

Контекст: Generics в TypeScript

Это вопрос о работе с Generic типами в TypeScript. Обе конструкции используются в объявлении типов, но они имеют совершенно разные смыслы.

T Extends - ограничение типа (Constraint)

T extends U означает: "T должен быть подтипом U" или "T совместим с U".

// T может быть только строкой
function processString<T extends string>(value: T): T {
  return value.toUpperCase() as T;
}

processString('hello');    // OK
processString(42);         // ERROR: Type 'number' is not assignable to type 'string'
// T может быть любым объектом с свойством name
function getName<T extends { name: string }>(obj: T): string {
  return obj.name;
}

getName({ name: 'John', age: 30 });  // OK
getName({ age: 30 });                // ERROR: Property 'name' is missing
// T может быть ключом объекта K
function getProperty<K extends string, T extends Record<K, any>>(
  obj: T,
  key: K
): T[K] {
  return obj[key];
}

const user = { name: 'John', age: 30 };
getProperty(user, 'name');  // OK, возвращает string
getProperty(user, 'email'); // ERROR: email не существует

T = - значение по умолчанию (Default Type)

T = U означает: "если T не указан, используй U как значение по умолчанию".

// Если тип не указан, T будет string
function process<T = string>(value: T): T {
  return value;
}

process('hello');        // T = string (вывод типа)
process<number>(42);     // T = number (явное указание)
process();              // ERROR: нужен аргумент
// Generic с дефолтным значением
type Response<T = unknown> = {
  data: T;
  status: number;
};

const res1: Response = { data: undefined, status: 200 };
const res2: Response<string> = { data: 'hello', status: 200 };

Различия в таблице

КонцепцияT extends UT = U
СмыслОграничение типаЗначение по умолчанию
ПроверкаTypeScript проверяет соответствиеTypeScript использует U, если T не указан
ИспользованиеОграничивает возможные типыПредоставляет fallback тип
ОшибкаПри передаче неподходящего типаЕсли дефолт тоже не подходит
Где писатьВ объявлении genericВ объявлении generic

Примеры различий

Пример 1: Простой случай

// T extends string - T ДОЛЖЕН быть string
function upper<T extends string>(str: T): T {
  return str.toUpperCase() as T;
}

upper('hello');    // OK
upper(123);        // ERROR: Type 'number' is not assignable

// T = string - если T не указан, используй string
function lower<T = string>(value: T): T {
  return value as any; // T может быть чем угодно
}

lower('hello');     // T = string
lower(123);         // T = number (OK!)
lower();            // ERROR: нужен аргумент

Пример 2: С объектами

// extends - ограничение структуры
function processUser<T extends { id: number; name: string }>(user: T) {
  console.log(user.id, user.name);
}

processUser({ id: 1, name: 'John' });              // OK
processUser({ id: 1, name: 'John', age: 30 });     // OK (больше свойств)
processUser({ id: 1 });                             // ERROR: name отсутствует

// = дефолт - используется если тип не указан
type User = { id: number; name: string };
function process<T = User>(data: T): T {
  return data;
}

process({ id: 1, name: 'John' });                  // T = User
process<{ email: string }>({ email: 'test' });     // T = { email: string }

Комбинирование extends и =

Можно использовать оба одновременно!

// T должен быть extends string, но по умолчанию = 'default'
function create<T extends string = 'default'>(value?: T): T {
  return (value || 'default') as T;
}

create();            // T = 'default' (используется дефолт)
create('custom');    // T = 'custom'
create(123);         // ERROR: number не extends string
// K должен быть extends keyof T, и по умолчанию = keyof T
function getProperty<T, K extends keyof T = keyof T>(obj: T, key?: K) {
  return key ? obj[key] : null;
}

const user = { name: 'John', age: 30 };
getProperty(user);              // K = keyof user
getProperty(user, 'name');      // K = 'name'
getProperty(user, 'invalid');   // ERROR

Реальные примеры из практики

Пример 1: Hook с типом данных

// Используем оба: ограничение И дефолт
function useApi<T extends object = Record<string, any>>(
  url: string
): Promise<T> {
  return fetch(url).then(r => r.json());
}

// Без явного типа - используется дефолт
const data1 = await useApi('/api/users');
// data1: Record<string, any>

// С явным типом
interface User { id: number; name: string; }
const data2 = await useApi<User>('/api/users/1');
// data2: User

Пример 2: Реакт компонент

// Дефолтный тип для props
function Button<T extends { onClick: () => void } = { onClick: () => {} }>(
  props: T
) {
  return <button onClick={props.onClick}>Click</button>;
}

const btn1 = <Button />; // T = { onClick: () => void }
const btn2 = <Button onClick={() => alert('hi')} />; // T = { onClick: () => void }

Пример 3: API wrapper

// ApiResponse может быть любого типа, по умолчанию null
class ApiClient<ResponseType = null> {
  async get<T = ResponseType>(url: string): Promise<T> {
    const response = await fetch(url);
    return response.json();
  }
}

const client = new ApiClient();
const data1 = await client.get('/api/users');
// data1: null (дефолт)

const data2 = await client.get<User>('/api/users/1');
// data2: User

Ошибки при неправильном использовании

// ОШИБКА: extends и = перепутаны
function wrong<T extends = string>(value: T) {}
// ^^ SyntaxError

// ПРАВИЛЬНО: сначала extends, потом =
function correct<T extends string = 'hello'>(value: T) {}

Визуализация

T extends string
├─ Проверка: "T совместим со string?"
├─ Передано 'hello' -> OK (string)
└─ Передано 123 -> ERROR (number не extends string)

T = string
├─ Вопрос: "T указан?"
├─ Да -> используй переданный тип
└─ Нет -> используй string как дефолт

Ключевые выводы

  • T extends U - "T ДОЛЖЕН быть совместим с U" - это ОГРАНИЧЕНИЕ
  • T = U - "если T не указан, используй U" - это ДЕФОЛТ
  • Оба работают вместе: <T extends string = 'default'> - T ограничен string, дефолт 'default'
  • extends проверяется при КОМПИЛЯЦИИ
  • = дефолт применяется при ИСПОЛЬЗОВАНИИ без явного типа
  • Порядок важен: сначала extends, потом =