Строгая типизация — основа качества
Включите все строгие опции в tsconfig.json для максимальной безопасности типов:
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedIndexedAccess": true
}
}
Utility Types для чистого кода
TypeScript предоставляет мощные утилитарные типы. Вот несколько примеров:
// Partial - делает все свойства опциональными
interface User {
id: number;
name: string;
email: string;
}
type PartialUser = Partial<User>;
// { id?: number; name?: string; email?: string; }
// Pick - выбирает только указанные свойства
type UserPreview = Pick<User, 'id' | 'name'>;
// { id: number; name: string; }
// Omit - исключает указанные свойства
type UserWithoutEmail = Omit<User, 'email'>;
// { id: number; name: string; }
Дженерики для переиспользуемого кода
Создавайте универсальные функции с помощью дженериков:
// Универсальная функция для API запросов
async function fetchData<T>(url: string): Promise<T> {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json() as Promise<T>;
}
// Использование
interface Post {
id: number;
title: string;
body: string;
}
const posts = await fetchData<Post[]>('/api/posts');
const singlePost = await fetchData<Post>('/api/posts/1');
Type Guards для безопасной работы с типами
Type guards помогают сузить тип переменной:
interface Dog {
type: 'dog';
bark(): void;
}
interface Cat {
type: 'cat';
meow(): void;
}
type Animal = Dog | Cat;
// Type guard функция
function isDog(animal: Animal): animal is Dog {
return animal.type === 'dog';
}
function makeSound(animal: Animal) {
if (isDog(animal)) {
animal.bark(); // TypeScript знает, что это Dog
} else {
animal.meow(); // TypeScript знает, что это Cat
}
}
Работа с React компонентами
Правильная типизация React компонентов:
import { FC, ReactNode, useState } from 'react';
// Props с children
interface CardProps {
title: string;
children: ReactNode;
onClick?: () => void;
}
const Card: FC<CardProps> = ({ title, children, onClick }) => {
return (
<div className="card" onClick={onClick}>
<h2>{title}</h2>
{children}
</div>
);
};
// Хуки с типами
interface User {
id: number;
name: string;
}
function useUser() {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true);
return { user, setUser, loading };
}
Интерфейсы vs Типы
Интерфейсы (interface) и типы (type) похожи, но есть различия. Интерфейсы можно расширять через declaration merging, типы поддерживают union и intersection.
Generics — мощь переиспользования
Generics (дженерики) позволяют создавать универсальный код. Включите strict mode в tsconfig для максимальной безопасности типов.
Инструменты разработки
Современные IDE (VS Code, WebStorm) предоставляют мощное автодополнение и рефакторинг для TypeScript. Компилятор tsc проверяет типы и выявляет ошибки до запуска кода, обеспечивая безопасность типов на этапе разработки.
Intersection типы объединяют несколько типов в один, что полезно для миксинов и расширения функциональности.
Заключение
Следуя этим практикам, вы сможете писать более надёжный и поддерживаемый код. TypeScript — это инвестиция в качество вашего проекта.
Помните: строгая типизация на этапе разработки предотвращает ошибки в продакшене.
Условные типы (Conditional Types)
// Условные типы позволяют создавать типы на основе условий
type IsString<T> = T extends string ? true : false;
type A = IsString<string>; // true
type B = IsString<number>; // false
// Практический пример: извлечение типа из Promise
type Awaited<T> = T extends Promise<infer U> ? U : T;
type Result = Awaited<Promise<string>>; // string
// Фильтрация union типов
type NonNullable<T> = T extends null | undefined ? never : T;
type Clean = NonNullable<string | null | undefined>; // string
Mapped Types
// Создание типов на основе существующих
interface User {
id: number;
name: string;
email: string;
}
// Делаем все поля readonly
type ReadonlyUser = {
readonly [K in keyof User]: User[K];
};
// Делаем все поля опциональными
type PartialUser = {
[K in keyof User]?: User[K];
};
// Добавляем префикс к ключам
type PrefixedUser = {
[K in keyof User as `user_${string & K}`]: User[K];
};
// { user_id: number; user_name: string; user_email: string; }
Декораторы (TypeScript 5+)
// Декоратор для логирования методов
function log(target: any, key: string, descriptor: PropertyDescriptor) {
const original = descriptor.value;
descriptor.value = function(...args: any[]) {
console.log(`Calling ${key} with:`, args);
const result = original.apply(this, args);
console.log(`Result:`, result);
return result;
};
return descriptor;
}
class Calculator {
@log
add(a: number, b: number): number {
return a + b;
}
}
const calc = new Calculator();
calc.add(2, 3);
// Calling add with: [2, 3]
// Result: 5
Паттерн Builder с типами
class QueryBuilder<T> {
private query: Partial<T> = {};
where<K extends keyof T>(key: K, value: T[K]): this {
this.query[key] = value;
return this;
}
build(): Partial<T> {
return { ...this.query };
}
}
interface UserQuery {
name: string;
age: number;
active: boolean;
}
const query = new QueryBuilder<UserQuery>()
.where('name', 'John') // ✅ Типизировано
.where('age', 25) // ✅ Типизировано
// .where('name', 123) // ❌ Ошибка типа
.build();