Avatar ·

TypeScript: чем отличается export default от export и когда что использовать?

Всем привет! Я начал недавно изучать TypeScript (раньше писал на чистом JS) и запутался в экспортах. Вроде бы есть два способа: `export default` и просто `export`. Но я не понимаю, когда какой применять. Вот пример. У меня есть файл `user.ts`: ```typescript // user.ts export default class User { constructor(public name: string) {} } export function formatUser(user: User): string { return `User: ${user.name}`; } ``` И в другом файле я импортирую: ```typescript // app.ts import User from './user'; // так работает import { formatUser } from './user'; // так тоже работает ``` Но я видел, что некоторые пишут `import * as UserModule from './user'`. А ещё можно импортировать default без фигурных скобок, а именованные — только в фигурных. Почему так? И главный вопрос: когда лучше делать `export default`, а когда `export`? Есть ли какие-то правила или best practices? Вроде в React часто экспортируют компоненты через `export default`, а утилиты — через именованный экспорт. Это правильно? Буду благодарен за развёрнутое объяснение с примерами! <<>>

Ключевое различие: именованный экспорт vs экспорт по умолчанию

В TypeScript (и ES6) существует два основных способа экспорта:

  • Именованный экспорт — export const/function/class
  • Экспорт по умолчанию — export default

Главное отличие — количество и способ импорта.

Именованный экспорт (named export)

Позволяет экспортировать множество сущностей из одного модуля. При импорте нужно обязательно использовать те же имена в фигурных скобках:

// math.ts
export const PI = 3.14;
export function sum(a: number, b: number): number {
  return a + b;
}
export class Calculator { /* ... */ }

// app.ts
import { PI, sum, Calculator } from './math';

Особенности:

  • Имя при импорте должно совпадать с именем экспорта
  • Можно использовать as для переименования: import { sum as add } from './math'
  • Можно импортировать всё сразу: import * as Math from './math' — тогда Math.PI, Math.sum и т.д.

Экспорт по умолчанию (default export)

Позволяет экспортировать одну главную сущность из модуля. При импорте можно дать любое имя:

// logger.ts
export default class Logger {
  log(message: string): void {
    console.log(message);
  }
}

// app.ts
import MyLogger from './logger'; // имя может быть любым
// или
import Logger from './logger'; // обычно называют как в исходнике

Особенности:

  • В модуле может быть только один export default
  • Имя при импорте выбираете вы сами (это может привести к путанице)
  • Не нужно использовать фигурные скобки

Когда что использовать? Best practices

Используйте именованный экспорт, когда:

  • Модуль содержит несколько функций/классов/констант
  • Вы хотите явно указать, что именно экспортируется
  • Вы работаете в команде — именованные экспорты легче рефакторить и искать
  • Пример: утилиты, хелперы, константы, типы

Используйте экспорт по умолчанию, когда:

  • Модуль представляет одну главную сущность (один класс, одну функцию)
  • Это компонент React (традиция, но не строгое правило)
  • Пример: export default class App, export default function App()

Практический пример: смешанный экспорт

Часто используют оба подхода в одном файле:

// user.ts
export default class User {
  constructor(public name: string) {}
}

export function createUser(name: string): User {
  return new User(name);
}

export const DEFAULT_USER = new User('Guest');

// app.ts
import User, { createUser, DEFAULT_USER } from './user';

Здесь User — главная сущность (экспорт по умолчанию), а createUser и DEFAULT_USER — дополнительные (именованные).

Рекомендация от TypeScript-сообщества

Многие разработчики (включая авторов популярных библиотек) склоняются к тому, чтобы избегать export default в пользу именованных экспортов. Причины:

  • Лучшая поддержка автодополнения в IDE
  • Проще рефакторинг (переименование)
  • Единообразие кода
  • Меньше путаницы с именами при импорте

Однако для React-компонентов и одного главного экспорта — default всё ещё широко используется.

Итог

Выбор между export и export default зависит от контекста. Для маленьких модулей с одной сущностью — default. Для библиотек, утилит и всего, где важна ясность — именованный экспорт. Можно комбинировать оба подхода в одном файле.

<<>>

Альтернативный взгляд: всегда используйте именованные экспорты

Есть подход, который набирает популярность в TypeScript-сообществе: вообще отказаться от export default. Вот аргументы:

  • Единообразие импорта — всегда используете фигурные скобки, нет путаницы с синтаксисом
  • Лучшая поддержка рефакторинга — IDE может переименовать экспорт и все импорты сразу
  • Tree shaking — сборщики (Webpack, Vite) лучше оптимизируют именованные экспорты
  • Явность — вы всегда знаете, что импортируете, имя не может быть изменено случайно

Пример реализации:

// user.ts
export class User {
  constructor(public name: string) {}
}

export function formatUser(user: User): string {
  return `User: ${user.name}`;
}

// app.ts
import { User, formatUser } from './user';

Если нужно экспортировать "главную" сущность, просто называйте файл соответственно (например, user.ts экспортирует User). Это делает код более предсказуемым и упрощает работу в больших проектах.

Многие современные библиотеки (например, TypeScript сам, Lodash, RxJS) переходят на именованные экспорты. React — одно из немногих исключений, где default export остаётся стандартом де-факто.