← Назад к вопросам

Пишешь на TypeScript

1.3 Junior🔥 91 комментариев
#TypeScript

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Опыт работы с TypeScript

Да, я активно использую TypeScript в своей работе, особенно в React проектах. Переход на TypeScript значительно улучшил качество кода и скорость разработки в долгосрочной перспективе.

Почему я выбираю TypeScript

1. Типобезопасность (Type Safety)

TypeScript ловит ошибки на этапе разработки, а не в production:

interface User {
  id: string;
  name: string;
  email: string;
}

function getUserInfo(user: User): string {
  return `${user.name} (${user.email})`;
}

// TypeScript ошибка: свойство 'email' не существует на числе
const info = getUserInfo({ id: '123', name: 'John' });

// Исправлено
const info = getUserInfo({ 
  id: '123', 
  name: 'John',
  email: 'john@example.com'
});

2. Автодополнение и IDE поддержка

IDE (VS Code, WebStorm) показывают доступные методы и свойства:

const user: User = { id: '1', name: 'John', email: 'john@example.com' };

// IDE подсказывает: id, name, email (автодополнение работает)
user.email;

3. Самодокументируемый код

Типы служат документацией:

// Вместо комментариев, типы показывают структуру данных
function fetchUsers(limit: number, offset: number): Promise<User[]> {
  // Понятно, что функция возвращает Promise с массивом User
}

// Vs JavaScript комментарий (который может устаревать)
// function fetchUsers(limit, offset) {
//   // Returns array of users
// }

Как я использую TypeScript

React компоненты с типизацией:

interface ButtonProps {
  variant?: 'primary' | 'secondary' | 'danger';
  size?: 'small' | 'medium' | 'large';
  disabled?: boolean;
  onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
  children: React.ReactNode;
}

export function Button({
  variant = 'primary',
  size = 'medium',
  disabled = false,
  onClick,
  children
}: ButtonProps): React.ReactElement {
  return (
    <button 
      className={`btn btn-${variant} btn-${size}`}
      disabled={disabled}
      onClick={onClick}
    >
      {children}
    </button>
  );
}

Работа с API (типизация ответов):

interface ApiResponse<T> {
  data: T;
  status: 'success' | 'error';
  message?: string;
}

interface Article {
  id: string;
  title: string;
  content: string;
  publishedAt: Date;
}

async function fetchArticles(): Promise<Article[]> {
  const response = await fetch('/api/articles');
  const json: ApiResponse<Article[]> = await response.json();
  
  if (json.status === 'error') {
    throw new Error(json.message);
  }
  
  return json.data;
}

Кастомные хуки:

interface UseFetchState<T> {
  data: T | null;
  loading: boolean;
  error: Error | null;
}

function useFetch<T>(url: string): UseFetchState<T> {
  const [state, setState] = useState<UseFetchState<T>>({
    data: null,
    loading: true,
    error: null
  });

  useEffect(() => {
    fetch(url)
      .then(res => res.json())
      .then(data => setState({ data, loading: false, error: null }))
      .catch(error => setState({ data: null, loading: false, error }));
  }, [url]);

  return state;
}

// Использование
const { data: articles, loading, error } = useFetch<Article[]>('/api/articles');

Utility Types (полезные встроенные типы):

// Omit - исключить поля
type UserWithoutEmail = Omit<User, 'email'>;

// Pick - выбрать только нужные поля
type UserPreview = Pick<User, 'id' | 'name'>;

// Partial - все поля опциональны
type UserUpdate = Partial<User>;

// Required - все поля обязательны
type UserRequired = Required<User>;

// Record - для объектов с определёнными ключами
type Status = 'active' | 'inactive' | 'pending';
type UsersByStatus = Record<Status, User[]>;
// { active: [...], inactive: [...], pending: [...] }

Паттерны, которые я использую

1. Type Guard для runtime проверок:

function isUser(obj: unknown): obj is User {
  return (
    typeof obj === 'object' &&
    obj !== null &&
    'id' in obj &&
    'name' in obj &&
    'email' in obj
  );
}

// Использование
const data: unknown = fetchData();
if (isUser(data)) {
  console.log(data.name); // TypeScript знает, что это User
}

2. Discriminated Unions для обработки разных состояний:

type LoadingState = { status: 'loading' };
type SuccessState = { status: 'success'; data: Article[] };
type ErrorState = { status: 'error'; error: string };

type FetchState = LoadingState | SuccessState | ErrorState;

function renderState(state: FetchState) {
  switch (state.status) {
    case 'loading':
      return <Spinner />;
    case 'success':
      // TypeScript знает, что здесь есть data
      return <ArticleList articles={state.data} />;
    case 'error':
      // TypeScript знает, что здесь есть error
      return <ErrorMessage message={state.error} />;
  }
}

3. Generic компоненты:

interface ListProps<T> {
  items: T[];
  renderItem: (item: T) => React.ReactNode;
  keyExtractor: (item: T) => string;
}

function List<T>({
  items,
  renderItem,
  keyExtractor
}: ListProps<T>) {
  return (
    <ul>
      {items.map(item => (
        <li key={keyExtractor(item)}>{renderItem(item)}</li>
      ))}
    </ul>
  );
}

// Использование с разными типами
<List<User>
  items={users}
  keyExtractor={user => user.id}
  renderItem={user => user.name}
/>

<List<Article>
  items={articles}
  keyExtractor={article => article.id}
  renderItem={article => article.title}
/>

Конфигурация TypeScript

В своих проектах использую strict режим:

{
  "compilerOptions": {
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "noImplicitThis": true,
    "alwaysStrict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "esModuleInterop": true,
    "module": "esnext",
    "target": "es2020"
  }
}

Проблемы, которые я решал

1. Сложность типов (over-typing):

// Плохо: слишком сложно
type ComplexType = Readonly<Record<keyof User, Omit<User, 'id'>>>;

// Хорошо: просто и понятно
interface UserPreview {
  name: string;
  email: string;
}

2. Any тип (убегал от типизации):

// Плохо: теряем все преимущества TypeScript
const data: any = fetchData();

// Хорошо: используем unknown и type guards
const data: unknown = fetchData();
if (isUser(data)) {
  // Здесь данные типизированы
}

Вывод

TypeScript стоит той цены, которую приходится платить на обучение. В начале кажется, что это замедляет разработку, но на практике:

  • Экономит время на отладку ошибок
  • Упрощает рефакторинг (нужно обновить типы)
  • Делает код более понятным для команды
  • Улучшает IDE поддержку
  • Снижает количество runtime ошибок

Для любого проекта размером больше 2000 строк кода TypeScript - значительный плюс.

Пишешь на TypeScript | PrepBro