typescript
November 9, 2019

Причины отказаться от дефолтных экспортов в JavaScript

В ES6 существует 2 способа экспортировать классы, функции и переменные из модулей - именованный экспорт и экспорт по умолчанию.

Именованный экспорт

Для именованного экспорта используется ключевое слово export рядом с экспортируемым классом, функцией или переменной. При импорте используются фигурные скобки:

export class SmsSender {
  sendMessage(phone, text) { ... }
}

...

import { SmsSender } from './sms-sender';

Дефолтный экспорт

Для экспорта по умолчанию используется export default. При импорте фигурные скобки не используются:

export default class SmsSender {
  sendMessage(phone, text) { ... }
}

...

import SmsSender from './sms-sender';

Дефолтные экспорты имеют ряд недостатков по сравнению с именованными.

Возможное расхождение имён в импортах и экспортах

В модуле может быть максимум 1 default export, поэтому экспортируемая сущность может не иметь имени. По этой же причине не накладывается ограничение на то, под каким именем сущность импортируется, что на практике приводит к проблемам - если класс переименуют, то могут забыть обновить имя во всех импортах. В отличии от именованных экспортов, дефолтные экспорты дают возможность импортировать одну и ту же сущность под разными именами в разных местах, что нередко встречается в больших проектах. Необходимость помнить, что сущность встречается в коде под несколькими именами увеличивает когнитивную нагрузку, так как нужно запоминать всё больше малозначимых деталей.

Более простой реэкспорт

Использование реэкспорта оправдано, если вы пишете библиотеку для всеобщего использования. Реэкспорт позволяет пользователям библиотеки использовать только те модули, что экспортированы наружу, делая оставшиеся модули приватными и недоступными извне. Для приватных модулей не требуется соблюдать обратную совместимость, а значит вы можете менять внутренности как угодно, не боясь, что у пользователей библиотеки что-то отвалится. Если все экспорты именованные, то реэкспортировать их можно так:

export * from './canvas'; 

Про дефолтные экспорты требуется помнить, что они обрабатываются по-особенному:

export * from './canvas';
export { default as Canvas } from './canvas';

Отсутствие автоматического импорта IDE

VSCode и WebStorm могут автоматически вставлять импорты для дефолтных и именованных экспортов. Однако если экспортируемая сущность не имеет имени, то автоматическая подстановка импортов работать не будет. Такие функции встречаются даже в популярных open-source проектах:

export default function<T>(store: T, config?: RemoteDevConfig): T;

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

Дефолтный экспорт анонимной функции

В проектах на React иногда можно встретить такую конструкцию:

export default ({ onClick }) => {
  ...
}

Из-за того, что функция безымянная, такой компонент будет отображён в React Devtools как Unknown, так как React даёт имя компоненту исходя из названия функции или класса. Большое количество безымянных компонентов усложнит отладку в Devtools.

Итог

Таким образом, использование именованных экспортов вместо дефолтных избавляет нас от случайной сложности, упрощает код за счёт единообразия, позволяя меньше думать над тем, каким способом экспортировать классы, функции и переменные. Для запрета дефолтных экспортов можно использовать ESLint и TSLint правила: