Комментарии (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;
}
Вывод
- Generics позволяют писать переиспользуемый код сохраняя типизацию
- Используй <T> для параметра типа и TypeScript автоматически определит его
- Ограничения (extends) помогают сузить возможные типы
- Generic интерфейсы и функции улучшают типизацию API
- В React компонентах Generics делают их переиспользуемыми
- Профайли вместе с коллегами чтобы убедиться что тип правильный