Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Для чего используются условные типы
Условные типы (Conditional Types) - это мощная функция TypeScript, которая позволяет выбирать тип на основе другого типа. Это как if/else для типов.
Синтаксис условных типов
type ConditionalType = T extends U ? X : Y;
Читается так:
- Если тип T расширяет (extends) тип U
- То используй тип X
- Иначе используй тип Y
Простой пример
type IsString<T> = T extends string ? true : false;
type A = IsString<"hello">; // true
type B = IsString<42>; // false
type C = IsString<string>; // true
Где они используются
1. Определение типа на основе значения
// Если передали строку, вернём length (число)
// Если массив, вернём его тип элемента
type Flatten<T> = T extends any[] ? T[0] : T;
type Str = Flatten<string[]>; // string
type Num = Flatten<number>; // number
type Arr = Flatten<[1, 2, 3]>; // 1 | 2 | 3
2. Извлечение типов из Union
// Достаёт функции из Union типов
type ExtractFunction<T> = T extends (...args: any[]) => any ? T : never;
type FuncType = ExtractFunction<string | ((x: number) => void) | boolean>;
// Результат: (x: number) => void
3. Условные типы с объектами
type HasProperty<T, K extends PropertyKey> = K extends keyof T ? true : false;
interface User {
name: string;
email: string;
}
type HasName = HasProperty<User, 'name'>; // true
type HasAge = HasProperty<User, 'age'>; // false
4. Превращение Promise в значение
// Если передали Promise<T>, вернём T
// Иначе вернём T как есть
type Unwrap<T> = T extends Promise<infer U> ? U : T;
type A = Unwrap<Promise<string>>; // string
type B = Unwrap<number>; // number
type C = Unwrap<Promise<{id: number}>>; // {id: number}
5. Практический пример: API ответы
type ApiResponse<T> = T extends 'loading' ? null : T extends 'error' ? Error : T;
// Состояние загрузки -> null
type LoadingState = ApiResponse<'loading'>; // null
// Состояние ошибки -> Error
type ErrorState = ApiResponse<'error'>; // Error
// Данные -> сами данные
type DataState = ApiResponse<User>; // User
6. Условные типы с infer
infer позволяет "вытащить" тип из структуры:
// Вытащим аргументы функции
type GetArgs<T> = T extends (...args: infer A) => any ? A : never;
function add(a: number, b: number): number {
return a + b;
}
type AddArgs = GetArgs<typeof add>; // [a: number, b: number]
7. Извлечение типа элемента из массива
type ArrayElement<T> = T extends Array<infer U> ? U : never;
type StringArrayElement = ArrayElement<string[]>; // string
type NumberArrayElement = ArrayElement<number[]>; // number
type NotAnArray = ArrayElement<string>; // never
Реальный пример: Генерик хук в React
interface ApiData {
users: { id: number; name: string }[];
posts: { id: number; title: string }[];
}
// Если T = 'users', вернём User[]
// Если T = 'posts', вернём Post[]
type GetApiData<K extends keyof ApiData> = ApiData[K];
function useApi<K extends keyof ApiData>(endpoint: K) {
const [data, setData] = useState<GetApiData<K> | null>(null);
// ...
return { data };
}
const usersHook = useApi('users');
// data имеет тип: {id: number; name: string}[] | null
const postsHook = useApi('posts');
// data имеет тип: {id: number; title: string}[] | null
Условные типы с Union
// Распределительные условные типы
type ToArray<T> = T extends any ? T[] : never;
type StrOrNum = ToArray<string | number>;
// Результат: string[] | number[]
// (не (string | number)[])
Это полезно для трансформации union типов:
// Вытащим функции из union
type FunctionType = string | number | (() => void) | boolean;
type OnlyFunctions<T> = T extends (...args: any[]) => any ? T : never;
type Funcs = OnlyFunctions<FunctionType>; // () => void
Сложный пример: Типизация Action Creators
type ActionCreator<T> = (payload: T) => { type: string; payload: T };
type ExtractPayload<T> = T extends ActionCreator<infer P> ? P : never;
const userCreator = (id: number) => ({ type: 'SET_USER', payload: id });
type UserPayload = ExtractPayload<typeof userCreator>; // number
const settingsCreator = (config: { theme: string }) => ({
type: 'SET_SETTINGS',
payload: config
});
type SettingsPayload = ExtractPayload<typeof settingsCreator>; // { theme: string }
Когда они полезны
1. Создание гибких типов:
// Функция работает с любым типом, но тип результата зависит от входа
function getValue<T>(value: T): T extends string ? string : T extends number ? number : T {
return value;
}
const str = getValue('hello'); // type: string
const num = getValue(42); // type: number
const obj = getValue({foo: 'bar'}); // type: {foo: string}
2. Построение типов на основе других типов:
type ReadOnly<T> = T extends object
? { readonly [K in keyof T]: T[K] }
: T;
type User = { name: string; age: number };
type ReadOnlyUser = ReadOnly<User>;
// { readonly name: string; readonly age: number }
3. Фильтрация типов:
// Достанет только строковые ключи
type StringPropertiesOnly<T> = {
[K in keyof T as T[K] extends string ? K : never]: T[K]
};
type User = { name: string; age: number; email: string };
type StringProps = StringPropertiesOnly<User>;
// { name: string; email: string }
Частые ошибки
Ошибка 1: Забыли про never
// НЕПРАВИЛЬНО: сложно потом отладить
type GetString<T> = T extends string ? T : any;
// ПРАВИЛЬНО: явно показываем, что не подходит
type GetString<T> = T extends string ? T : never;
Ошибка 2: Не понимаешь распределение
// Это распределяется!
type ToArray<T> = T extends any ? T[] : never;
type Result = ToArray<string | number>; // string[] | number[]
// Если не хочешь распределения, оберни в []:
type ToArray2<T> = [T] extends [any] ? T[] : never;
type Result2 = ToArray2<string | number>; // (string | number)[]
Итог
- Условные типы позволяют выбирать тип на основе другого типа
- Используй для гибких API и генериков
- infer помогает вытащить тип из структуры
- Распределение применяется к union типам
- Они делают TypeScript код более мощным и переиспользуемым