← Назад к вопросам
Для чего используется слово 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 кода!