Назад к блогу
Разработка

TypeScript Best Practices 2024: Пишем чистый код

Современные практики TypeScript: строгая типизация, utility types, паттерны и антипаттерны.

5 января 2026 г.
8 мин чтения
73 просмотров
MOLOTILO

MOLOTILO DIGITAL

TypeScript Best Practices 2024: Пишем чистый код

Строгая типизация — основа качества

Включите все строгие опции в 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();
TypeScriptBest PracticesClean Code

Понравилась статья?

Подпишитесь на наш блог, чтобы не пропустить новые материалы