Почему вы должны предпочесть именованный экспорт экспорту по умолчанию

Если вы являетесь разработчиком Javascript/Typescript и никогда не задумывались о разнице (я имею в виду на практике) или плюсах и минусах использования именованного экспорта или экспорта по умолчанию, пришло время это понять.

Прежде чем продолжить, важно сказать, что этот пост содержит мое личное мнение с учетом моего опыта использования JS/TS и знаний, полученных из других источников.

Экспорт по умолчанию: как это работает и в чем проблема

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

/* === fruits.ts === */
const fruits = ["Apple", "Banana", "Orange"]
export default fruits

/* === index.ts === */
import fruits from "./fruits"
console.log(fruits)

Есть две вещи, которые я хочу отметить в отношении экспорта по умолчанию:

  1. Он может существовать один раз в файле
  2. При импорте этого значения в другой файл оно может принимать имя, которое хочет разработчик — нет необходимости иметь то же имя, что и экспортируемое.

Итак, теперь вы можете посмотреть на то, что читали до этого, и спросить: «Хорошо, но… что еще? А проблема?».

Чтобы эти темы стали проблемой, это будет зависеть от размера приложения. Если мы подумаем о небольшом приложении, у него, вероятно, не будет большой кодовой базы, и оно, как правило, будет простым, поэтому эти темы не будут проблемой. С другой стороны, если мы думаем о приложении среднего размера, оно, вероятно, имеет большую кодовую базу с множеством различных файлов, взаимодействующих друг с другом. Неосторожность в этом случае может привести к беспорядку.

Давайте продолжим разговор, имея в виду приложение среднего размера, и посмотрим, что стоит за этими темами.

Он может существовать один раз в файле

Проблема в том, что вы можете создать новый файл, который будет экспортировать только одну вещь (только сейчас), как показано ниже…

/* === UserType.ts === */
enum UserType {
  ADMIN = "ADMIN",
  BASIC = "BASIC"
}
export default UserType

Но что, если мы хотим экспортировать функцию, которая возвращает значение перечисления из строкового ввода, например:

/* === UserType.ts === */
enum UserType {
  ADMIN = "ADMIN",
  BASIC = "BASIC"
}

export function getUserType(userType: string) {
  switch (userType) {
    case UserType.ADMIN:
      return UserType.ADMIN
    case UserType.BASIC:
      return UserType.BASIC
    default:
      return ""
  }
}

export default UserType

Обратите внимание, что я экспортировал эту новую функцию с помощью именованного экспорта, потому что, как мы уже видели, невозможно выполнить экспорт по умолчанию более одного раза. Продолжая эту строку, если мы посмотрим, как будет выглядеть импорт из этого файла, мы увидим, что он начинает выглядеть запутанно из-за такого сочетания различных экспортов (лично мне это не нравится).

import UserType, { getUserType } from "./UserType"

Вы можете подумать "о, но мне это подходит", но подумайте об этой ситуации с гораздо большим количеством экспортов из одного и того же файла. Наверное, это было бы некрасиво.

Дело в том, что я думаю, что лучше придерживаться единого способа экспорта/импорта значений, хорошо? Чище, проще.

При импорте он может принимать имя, которое хочет разработчик.

Некоторые люди скажут, что это обеспечивает гибкость или что это нормально, но это может серьезно нарушить вашу организацию базы кода и отслеживание файлов — для меня это худшая часть экспорта по умолчанию.

Это может быть плохо по-разному, например:

  1. При импорте значения по умолчанию в файл вы можете изменить его имя, и оно лишится соответствующего значения исходного имени, что ухудшит читаемость и связность кода.
  2. При копировании/вставке или дублировании файлов так легко забыть переименовать эти дубликаты или даже не заметить, что вы, должно быть, переименовали их.
  3. Если вы импортируете значение, дав ему другое имя, это ухудшит отслеживание/поиск позже, если кто-то захочет найти использование этого значения.

Подумайте об этом, вы не согласны? Экспорт по умолчанию может снизить качество вашего кода, даже если вы этого не заметите. Это просто объяснить, просто понять, но это очень важная вещь. Итак, теперь давайте посмотрим, как можно улучшить именованный экспорт.

Именованные экспорты: как они работают и чем они лучше

Именованные экспорты используются следующим образом:

/* === fruits.ts === */
export const fruits = ["Apple", "Banana", "Orange"]
// or
const fruits = ["Apple", "Banana", "Orange"]
export { fruits }

/* === index.ts === */
import { fruits } from "./fruits"
console.log(fruits)

Что следует учитывать при таком способе экспорта:

  1. При импорте вы должны использовать то же имя, что и для экспорта (за исключением случаев, когда вам действительно нужно переименовать значение из-за конфликта имен, чтобы вы могли это сделать)
  2. Вы можете иметь столько именованных экспортов, сколько хотите.

Таким образом, это почти противоположно экспорту по умолчанию. У вас все еще есть некоторая «гибкость», например, возможность переименовать значение, если это необходимо, но в основном у вас есть решение для некоторых проблем, которые возникают при экспорте по умолчанию.

Вы должны использовать то же имя, которое используется для экспорта

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

/* === UserType.ts === */
export enum UserType {
  ADMIN = "ADMIN",
  BASIC = "BASIC"
}

/* === index.ts === */
// 1. this one would throw an error due to unknown exported value
import { UserTypes } from "./UserType"
// 2. this one would work fine
import { UserType } from "./UserType"
// 3. and if you need to rename...
import { UserType as UserTypes } from "./UserType"

Это отличная вещь, потому что она не позволяет забыть переименовать значения при копировании/вставке или дублировании кода и обеспечивает согласованное использование имен, что делает наш код более значимым. Наконец, это также очень полезно для отслеживания/поиска кода, поскольку имя значения почти всегда будет одним и тем же, поэтому его легче найти.

Вы можете иметь столько именованных экспортов, сколько хотите.

Нужно экспортировать только одно значение? Два? Четыре? Десять? Это нормально, вы можете делать столько, сколько хотите. Лучше всего то, что вы можете делать это много раз, следуя уникальным путем, не смешивая различные способы экспорта и импорта. Это поможет вам организовать и стандартизировать кодовую базу.

/* === UserType.ts === */
export enum UserType {
  ADMIN = "ADMIN",
  BASIC = "BASIC"
}

export function getUserType(userType: string) {
  switch (userType) {
    case UserType.ADMIN:
      return UserType.ADMIN
    case UserType.BASIC:
      return UserType.BASIC
    default:
      return ""
  }
}

/* === index.ts === */
import { UserType, getUserType } from "./UserType"

Заключение

Именованный экспорт обычно лучше использовать в средних/крупных приложениях по некоторым причинам:

  • Организация кода
  • Отслеживание/поиск кода
  • Последовательность и стандартизация
  • Сохранение смысла кода
  • Избегайте ошибок при дублировании кода

Вы согласны со мной сейчас? Прокомментируйте этот пост своим мнением, дайте мне знать, что вы думаете об этом. Надеюсь, я помог вам узнать больше об этих различных способах экспорта значений в Javascript. Спасибо за внимание!