Default export
export default, import без фигурных скобок, когда использовать
📖 Введение
Представьте, что вы заходите в библиотеку. В каталоге написано: «Книга: "Война и мир" — Толстой Л.Н.». Вы знаете, что ищете именно эту книгу. Но что если бы в каталоге было написано: «Имена: Толстой Л.Н., Пушкин А.С., Достоевский Ф.М., Чехов А.П.»? Вы бы должны были выбрать нужное имя из списка.
Именно так работают именованные экспорты — вы перечисляете конкретные имена. Но что если модуль экспортирует только одну основную вещь? Тогда зачем заставлять пользователя каждый раз писать имя в фигурных скобках?
Default export (экспорт по умолчанию) — это специальный тип экспорта, который позволяет импортировать модуль без указания имени в фигурных скобках. Это удобно, когда модуль экспортирует одну главную сущность — класс, функцию или объект.
Давайте разберём подробнее, как работает default export, чем он отличается от именованного, и когда его стоит использовать.
1 Named export vs Default export — в чём разница
Давайте сравним два типа экспорта на простом примере:
// Named export
export const VERSION = '1.0.0';
export function greet(name: string): string {
return `Привет, ${name}!`;
}
// Импорт:
import { VERSION, greet } from './utils';
// Default export
export default function greet(name: string): string {
return `Привет, ${name}!`;
}
// Импорт:
import greet from './utils';
Ключевые отличия:
- Default export не требует имени в фигурных скобках при импорте
- Default export можно импортировать под любым именем
- В модуле может быть только один default export
- Default export можно комбинировать с именованными экспортами
- Named export — более строгий: имя при импорте должно совпадать с экспортом
Аналогия: Именованный экспорт — это меню в ресторане: вы выбираете конкретное блюдо. Default экспорт — это «комплексный обед»: вам дают набор по умолчанию, и вы его просто берёте без выбора.
Ещё одна аналогия: именованный экспорт — это полка в магазине с подписанными продуктами. Вы берёте именно то, что нужно. Default экспорт — это коробка-сюрприз: внутри одна главная вещь, и вы её просто забираете.
2 Синтаксис default export — три способа
Есть три способа создать default export:
1. Экспорт сразу при объявлении:
// Экспорт функции
export default function calculateTotal(price: number, quantity: number): number {
return price * quantity;
}
// Экспорт класса
export default class User {
constructor(public name: string, public email: string) {}
}
// Экспорт константы
export default const APP_NAME = 'My App';
// Экспорт объекта
export default {
apiUrl: 'https://api.example.com',
timeout: 5000,
retries: 3
};
2. Отдельный export default в конце файла:
function calculateTotal(price: number, quantity: number): number {
return price * quantity;
}
// Экспорт уже объявленной функции
export default calculateTotal;
// Или класса:
class UserService {
// ...
}
export default UserService;
3. Анонимный default export:
export default function(price: number, quantity: number): number {
return price * quantity;
}
Третий способ используется редко, потому что функция не имеет имени — это затрудняет отладку и чтение стек-трейсов. Когда вы видите ошибку в консоли, анонимная функция будет отображаться как <anonymous>, что не помогает понять, где именно произошла ошибка.
Совет: Всегда давайте имена default export-ам. Это упрощает чтение кода, отладку и поиск по проекту.
3 Синтаксис default import — без фигурных скобок
Главное преимущество default export — вы импортируете его без фигурных скобок и можете назвать как угодно:
// Все три варианта эквивалентны:
import calculateTotal from './math';
import calcTotal from './math';
import total from './math';
// Все ссылаются на одну и ту же функцию
console.log(calculateTotal(10, 5)); // 50
console.log(calcTotal(10, 5)); // 50
console.log(total(10, 5)); // 50
Вы можете назвать default импорт как хотите — TypeScript не проверяет, совпадает ли имя с оригиналом. Это удобно, но и опасно: если в проекте много файлов с default импортами, становится трудно понять, откуда берётся функция.
Смешанный импорт: Можно импортировать и default, и именованные экспорты одновременно:
// math.ts
export default function add(a: number, b: number): number {
return a + b;
}
export function subtract(a: number, b: number): number {
return a - b;
}
export const PI = 3.14159;
// app.ts
import add, { subtract, PI } from './math';
console.log(add(2, 3)); // 5
console.log(subtract(10, 4)); // 6
console.log(PI); // 3.14159
Обратите внимание: default импорт идёт без фигурных скобок и отделён запятой от именованных. Порядок не важен — можно сначала именованные, потом default:
// Все варианты эквивалентны:
import add, { subtract, PI } from './math';
import { subtract, PI }, add from './math'; // ❌ Синтаксическая ошибка
import { subtract } from './math';
import add from './math';
import { PI } from './math';
4 Когда использовать default export — правила и рекомендации
Default export хорош в следующих случаях:
- Компоненты React/Vue: каждый файл — один компонент, который является основным экспортом. Это стандартная конвенция в React-экосистеме.
- Утилитные модули с одной главной функцией: модуль для форматирования дат, модуль для валидации, модуль для шифрования.
- Конфигурационные объекты: файл
config.tsэкспортирует объект настроек. - Точка входа в библиотеку: главный модуль, который реэкспортирует всё необходимое.
- Фабричные функции: функция, которая создаёт и возвращает объект определённого типа.
Не используйте default export, когда:
- Модуль экспортирует несколько равнозначных сущностей
- Вы хотите явно показать, что именно импортируется
- Работаете в команде и хотите обеспечить консистентность кода
- Используете tree-shaking и хотите минимизировать размер бандла
- Модуль является утилитарным — содержит много функций
Пример: Модуль с несколькими функциями — лучше именованные экспорты:
// ❌ Не рекомендуется — неочевидно, что импортируется
export default { add, subtract, multiply };
// ✅ Рекомендуется — явно видно, что импортируется
export { add, subtract, multiply };
Правило большого пальца: Если модуль экспортирует одну главную сущность — используйте default. Если экспортирует несколько — используйте именованные.
5 Реэкспорт default export — переотправка
Иногда вам нужно переэкспортировать default export из другого модуля. Это делается через специальный синтаксис:
// utils/index.ts — barrel file
export { default } from './math';
// Теперь default export из math.ts доступен как default из index.ts
Если вы хотите переименовать default export при реэкспорте:
// utils/index.ts
export { default as calculate } from './math';
// Теперь это именованный экспорт
Или переэкспортировать всё из модуля (но default не включается):
// utils/index.ts
export * from './math';
export * from './dates';
// Default export НЕ переэкспортируется через export *
Чтобы переэкспортировать default вместе с именованными:
// utils/index.ts
export { default } from './math';
export * from './math'; // Именованные экспорты
export * from './dates';
Важно: export * не включает default export! Чтобы его переэкспортировать, используйте export { default } from.
⚠️ Типичные ошибки при работе с default export
1. Два default export в одном файле:
// ❌ Ошибка — два default export
export default function add() { /* ... */ }
export default function subtract() { /* ... */ } // Ошибка!
В модуле может быть только один default export. Если нужно экспортировать несколько функций — используйте именованные экспорты.
2. Импорт default как именованного:
// ❌ Неправильно — добавили фигурные скобки
import { add } from './math'; // add — это default export
// ✅ Правильно — без фигурных скобок
import add from './math';
// ✅ Или импортируем default под именем
import myAdd from './math';
Если вы используете фигурные скобки, TypeScript ищет именованный экспорт с именем add. Если он не найден — будет ошибка или undefined.
3. Забыли, что default можно импортировать под любым именем:
// math.ts
export default function add(a: number, b: number): number {
return a + b;
}
// app.ts — все три варианта рабочие
import add from './math'; // ✅
import myAdd from './math'; // ✅
import sum from './math'; // ✅
// Но только первый вариант очевиден — остальные запутывают
Совет: Всегда давайте default импорту имя, совпадающее с оригиналом. Это упрощает навигацию по коду и поиск по проекту.
4. Путаница с type-only import:
// ❌ Неправильно — import type для default export
import type User from './models/User'; // User — это класс, не тип
// ✅ Правильно — обычный импорт
import User from './models/User';
// ✅ Правильно — если это интерфейс или тип
import type { User } from './types';
import type используется только для типов, интерфейсов и псевдонимов типов. Если default export — это класс или функция, используйте обычный import.
🛠 Практический пример: React-компонент
Default export особенно популярен в React — каждый файл компонента экспортирует его как default:
// components/Button.tsx
interface ButtonProps {
label: string;
onClick: () => void;
variant?: 'primary' | 'secondary';
disabled?: boolean;
}
function Button({ label, onClick, variant = 'primary', disabled = false }: ButtonProps) {
const className = `btn btn-${variant}`;
return (
<button
className={className}
onClick={onClick}
disabled={disabled}
style={{
padding: '10px 20px',
borderRadius: '8px',
border: 'none',
cursor: disabled ? 'not-allowed' : 'pointer',
backgroundColor: variant === 'primary' ? '#3b82f6' : '#6b7280',
color: 'white'
}}
>
{label}
</button>
);
}
export default Button;
// components/index.ts — barrel file
export { default as Button } from './Button';
export { default as Card } from './Card';
export { default as Modal } from './Modal';
export { default as TextField } from './TextField';
Теперь компонент можно импортировать двумя способами:
// Способ 1: через barrel file (именованный экспорт)
import { Button } from './components';
// Способ 2: напрямую (default экспорт)
import Button from './components/Button';
⚖️ Именованные vs Default — итоговое сравнение
Named exports:
- Можно экспортировать сколько угодно из одного модуля
- Имя при импорте должно совпадать с экспортом (или использовать alias)
- Помогают с tree-shaking — бандлер знает, что именно используется
- Явно показывают, что именно импортируется
- Лучше для библиотек и больших проектов
- Код более предсказуемый — нет сюрпризов
Default export:
- Только один default на модуль
- Импортируется без фигурных скобок, можно назвать как угодно
- Не помогает с tree-shaking — бандлер не знает, что именно используется
- Удобен для компонентов и модулей с одной главной сущностью
- Популярен в React-экосистеме
- Код короче — не нужны фигурные скобки
7 Default export и TypeScript — типизация и автодополнение
TypeScript отлично работает с default export. Когда вы импортируете default export, TypeScript автоматически определяет его тип:
// utils.ts
export default function calculateTotal(price: number, quantity: number): number {
return price * quantity;
}
// app.ts
import calculateTotal from './utils';
// TypeScript знает типы:
const result = calculateTotal(10, 5); // result: number
const wrong = calculateTotal('10', 5); // ❌ Ошибка типа
Типизация default export класса:
// User.ts
export default class User {
constructor(
public name: string,
public email: string,
public age: number
) {}
getInfo(): string {
return `${this.name} (${this.email}), ${this.age} лет`;
}
}
// app.ts
import User from './User';
const user = new User('Алексей', 'alex@example.com', 25);
console.log(user.getInfo()); // ✅ TypeScript знает метод getInfo
Типизация default export интерфейса:
// types.ts
export default interface Config {
apiUrl: string;
timeout: number;
retries: number;
}
// app.ts
import Config from './types';
const config: Config = {
apiUrl: 'https://api.example.com',
timeout: 5000,
retries: 3
};
TypeScript обеспечивает полную типизацию для default export — автодополнение, проверку типов, рефакторинг. Это одна из причин, почему TypeScript так популярен.
8 Default export в других фреймворках — Angular, Vue
Default export используется не только в React. Вот как он используется в других фреймворках:
Vue:
<!-- components/MyButton.vue -->
<template>
<button @click="handleClick">
{{ label }}
</button>
</template>
<script setup lang="ts">
defineProps<{
label: string;
}>();
const handleClick = () => {
console.log('Clicked!');
};
</script>
// Импорт компонента
import MyButton from './components/MyButton.vue';
Angular:
// user.service.ts
import { Injectable } from '@angular/core';
@Injectable({ providedIn: 'root' })
export default class UserService {
getUser(id: number) {
return { id, name: 'Алексей' };
}
}
Во всех фреймворках default export используется для основного экспорта файла — компонента, сервиса, модуля.
✅ Итого
- export default — экспорт по умолчанию, один на модуль
- import name from './module' — импорт default без фигурных скобок
- Default export можно импортировать под любым именем
- Можно комбинировать с именованными:
import def, { named1, named2 } from './module' - Реэкспорт default:
export { default } from './module' - Хорош для компонентов и модулей с одной главной сущностью
- Используйте именованные экспорты для библиотек и больших проектов
- Не используйте анонимные default export — давайте имена
9 Default export и tree-shaking — влияние на размер бандла
Default export влияет на tree-shaking — процесс удаления неиспользуемого кода. Вот как это работает:
// utils.ts
export default function heavyFunction() {
// Тяжёлая функция
return 'result';
}
export function lightFunction() {
return 'light';
}
// app.ts — импортируем только lightFunction
import { lightFunction } from './utils';
Бандлер не может удалить heavyFunction, потому что default export — это неявная ссылка. Бандлер не знает, используете ли вы его где-то ещё.
С именованными экспортами tree-shaking работает лучше:
// utils.ts
export function heavyFunction() {
return 'result';
}
export function lightFunction() {
return 'light';
}
// app.ts — импортируем только lightFunction
import { lightFunction } from './utils';
Теперь бандлер может удалить heavyFunction, потому что видит явную ссылку только на lightFunction.
10 Default export и тестирование — как тестировать компоненты
При тестировании default export важно правильно импортировать компонент или функцию:
// Button.tsx
interface ButtonProps {
label: string;
onClick: () => void;
}
function Button({ label, onClick }: ButtonProps) {
return (
<button onClick={onClick}>{label}</button>
);
}
export default Button;
// Button.test.tsx
import { render, screen, fireEvent } from '@testing-library/react';
import Button from './Button'; // Импорт default export
describe('Button', () => {
it('renders with label', () => {
render(<Button label="Нажми меня" onClick={() => {}} />);
expect(screen.getByText('Нажми меня')).toBeInTheDocument();
});
it('calls onClick when clicked', () => {
const handleClick = jest.fn();
render(<Button label="Нажми меня" onClick={handleClick} />);
fireEvent.click(screen.getByText('Нажми меня'));
expect(handleClick).toHaveBeenCalledTimes(1);
});
});
11 Default export и рефакторинг — безопасное переименование
При рефакторинге default export безопаснее переходить на именованные экспорты:
// До рефакторинга
export default function processData(data: any) {
// ...
}
// После рефакторинга — экспорт по имени
export function processData(data: any) {
// ...
}
// Все файлы, которые импортировали default:
import processData from './utils';
// Теперь нужно импортировать по имени:
import { processData } from './utils';
IDE помогут автоматически обновить все импорты при рефакторинге. Используйте функцию «Rename Symbol» в вашей IDE.
12 Default export и React Router — маршрутизация
React Router также использует default export для страниц:
// pages/Home.tsx
export default function Home() {
return <h1>Главная страница</h1>;
}
// pages/About.tsx
export default function About() {
return <h1>О нас</h1>;
}
// App.tsx
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import Home from './pages/Home';
import About from './pages/About';
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</BrowserRouter>
);
}
export default App;
Проверяем понимание
5 вопросов