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

Для чего используется слово Extends?

1.6 Junior🔥 181 комментариев
#JavaScript Core#TypeScript

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

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

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

TypeScript Extends: Ограничение типов через наследование

Слово extends в TypeScript используется для создания отношений наследования между типами. Это один из самых важных ключевых слов для работы с типизацией и обобщёнными типами (generics).

Основные применения extends

1. Наследование интерфейсов (Interface Inheritance)

// Базовый интерфейс
interface Animal {
  name: string;
  age: number;
  makeSound(): void;
}

// Расширенный интерфейс
interface Dog extends Animal {
  breed: string;
  fetch(): void;
}

// Dog наследует все свойства Animal + добавляет свои
const myDog: Dog = {
  name: 'Rex',
  age: 3,
  breed: 'Labrador',
  makeSound: () => console.log('Woof!'),
  fetch: () => console.log('Fetching...'),
};

2. Наследование классов

class Animal {
  constructor(public name: string) {}

  makeSound() {
    console.log('Some sound');
  }
}

class Dog extends Animal {
  constructor(name: string, public breed: string) {
    super(name); // Вызов конструктора родителя
  }

  makeSound() {
    console.log('Woof!'); // Переопределение метода
  }

  fetch() {
    console.log('Fetching...');
  }
}

const dog = new Dog('Rex', 'Labrador');
dog.makeSound(); // Woof!

3. Constraints на Generic типы (Типовые ограничения)

Это одно из самых мощных применений extends — ограничить, какие типы можно передать в generic.

// Требуем, чтобы generic тип имел свойство name
interface Named {
  name: string;
}

function printName<T extends Named>(obj: T): void {
  console.log(obj.name); // Безопасно, потому что T гарантированно имеет name
}

printName({ name: 'John', age: 30 }); // OK
printName({ age: 30 }); // ERROR: нет свойства name

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

const person = { name: 'John', age: 30 };
const name = getProperty(person, 'name'); // OK, тип: string
getProperty(person, 'email'); // ERROR: 'email' не существует в person

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

Самое продвинутое использование — создание типов, которые выбирают другой тип в зависимости от условия.

// Синтаксис: T extends U ? X : Y
// Если T совместим с U, то тип X, иначе Y

// Пример 1: Проверка, является ли тип массивом
type IsArray<T> = T extends Array<any> ? true : false;

type A = IsArray<number[]>; // true
type B = IsArray<string>; // false

// Пример 2: Извлечение типа элемента массива
type ElementType<T> = T extends Array<infer U> ? U : T;

type StringArray = ElementType<string[]>; // string
type Number = ElementType<42>; // 42

// Пример 3: Практический пример в React
interface Props {
  value: unknown;
}

// Если value это функция, требуем параметр, иначе не нужен
type GetParamType<T> = T extends (...args: [infer P]) => any ? P : never;

function useData<T>(
  value: T,
  param?: GetParamType<T>
) {
  // ...
}

useData(() => 'hello', undefined); // OK
useData((name: string) => `Hello ${name}`, 'John'); // OK

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

Пример 1: Типобезопасная форма

// Требуем, чтобы generic тип был объектом
interface FormFieldProps<T extends Record<string, any>> {
  value: T;
  onChange: (newValue: T) => void;
}

// Функция работает только с объектами, имеющими свойства
function handleFormChange<T extends Record<string, any>>(
  obj: T,
  field: keyof T,
  value: any
): T {
  return { ...obj, [field]: value };
}

const user = { name: 'John', age: 30 };
const updated = handleFormChange(user, 'name', 'Jane'); // OK
handleFormChange(user, 'email', 'john@example.com'); // ERROR

Пример 2: Фильтрация свойств

// Получить все ключи, у которых значение определённого типа
type KeysOfType<T, V> = {
  [K in keyof T]: T[K] extends V ? K : never;
}[keyof T];

interface User {
  id: number;
  name: string;
  active: boolean;
  email: string;
}

// Получить ключи, где значение string
type StringKeys = KeysOfType<User, string>; // 'name' | 'email'
type NumberKeys = KeysOfType<User, number>; // 'id'

// Теперь можем создать функцию, которая работает только со строковыми полями
function setStringProperty<T, K extends KeysOfType<T, string>>(
  obj: T,
  key: K,
  value: string
) {
  obj[key] = value as any;
}

const user: User = { id: 1, name: 'John', active: true, email: 'john@example.com' };
setStringProperty(user, 'name', 'Jane'); // OK
setStringProperty(user, 'id', 'text'); // ERROR: id не string поле

Пример 3: Hook с типизацией

// Hook требует, чтобы значение было массивом или объектом
function useTracking<T extends object | any[]>(value: T, deps?: any[]) {
  useEffect(() => {
    console.log('Value changed:', value);
  }, deps || [value]);
}

useTracking([1, 2, 3]); // OK
useTracking({ name: 'John' }); // OK
useTracking('string'); // ERROR: string не extends object

// Более сложный пример: hook для fetch API
function useFetch<T extends string | URL>(
  url: T
): { data: any; loading: boolean; error: Error | null } {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    fetch(url)
      .then((r) => r.json())
      .then(setData)
      .catch(setError)
      .finally(() => setLoading(false));
  }, [url]);

  return { data, loading, error };
}

useFetch('https://api.example.com/users'); // OK
useFetch(new URL('https://example.com')); // OK
useFetch(123); // ERROR: число не extends string | URL

extends vs implements

Важно различать extends и implements:

// extends — наследование, расширение типа
interface Dog extends Animal {
  bark(): void;
}

// implements — класс должен соответствовать интерфейсу
class MyDog implements Dog {
  name = 'Rex';
  age = 3;

  makeSound() {
    console.log('Woof!');
  }

  bark() {
    console.log('Bark!');
  }
}

Распространённые паттерны с extends

Recursive типы

// Глубокие типы для вложенных структур
type DeepPartial<T> = {
  [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};

interface User {
  id: number;
  profile: {
    name: string;
    settings: {
      theme: string;
    };
  };
}

// Все свойства на любой глубине становятся optional
type PartialUser = DeepPartial<User>;

Распределительные условные типы

// Применить трансформацию к каждому типу в union
type Flatten<T> = T extends Array<infer U> ? U : T;

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

type Union = Flatten<string[] | number>; // string | number (применено к каждому)

Выводы

  • extends для интерфейсов — наследование и расширение типов
  • extends для классов — наследование и переопределение методов
  • extends для генериков — создание constraints (ограничений типов)
  • Условные типы (T extends U ? X : Y) — логика на уровне типов
  • Keyof + extends — создание типобезопасных операций

Овладение extends — это ключ к написанию типобезопасного и maintainable TypeScript кода!

Для чего используется слово Extends? | PrepBro