Импорт означает, что больше никогда не придется писать "требовать"

Вступление

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

Согласно Википедии, по состоянию на май 2017 года JavaScript поддерживает чуть менее 95% из 10 миллионов самых популярных веб-страниц.

Поскольку JS поддерживает большую часть Интернета, я хотел предоставить множество частей и примеров функций ES6 +, которые я регулярно использую, для других разработчиков.

Цель состоит в том, чтобы эти части были краткими и подробными объяснениями различных улучшений языка, которые, я надеюсь, вдохновят вас на написание действительно интересных вещей с использованием JS. Кто знает? Вы можете даже узнать что-то новое по пути.

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

Требование заменено модулями ES

Прежде чем я углублюсь во встроенные модули, позвольте мне дать вам очень краткий урок истории CommonJS и синтаксиса require.

До ES2015 наиболее распространенным подходом к добавлению различных библиотечных модулей в область видимости файлов JavaScript был CommonJS. Его использует Node.js, и сегодня это система, используемая большинством пакетов на npm. Основная концепция модулей CommonJS - это функция с именем require. Когда вы вызываете это с именем модуля зависимости, он проверяет, загружен ли модуль, и возвращает его интерфейс, который будет использоваться в области видимости файла.

Если вы когда-либо работали со сценариями Node.js или даже с более старыми клиентскими файлами JavaScript, вы, вероятно, уже видели этот синтаксис раньше. Вот пример импорта ExpressJS, широко используемого серверного фреймворка, в app.js файл Node.js:

Анатомия CommonJS требует

const express = require('express');

Просто объявив переменную express и вызвав require('express'), все различные методы ExpressJS теперь доступны для файла.

В течение долгого времени модули CommonJS работали достаточно хорошо и позволяли сообществу JavaScript через npm обмениваться кодом в больших масштабах.

Но у CommonJS есть несколько причуд, которые делают его менее чем идеальным решением: а именно тот факт, что require является обычным вызовом функции, принимающим любой аргумент, а не только строковый литерал, что затрудняет определение зависимостей модуля без запускает свой код. Есть еще несколько вопросов, но я не буду здесь вдаваться в подробности. В конце концов, это краткая история.

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

Если вы хотите глубже погрузиться в модули, пакеты и CommonJS, я бы рекомендовал прочитать эту главу из Eloquent JavaScript.

Но пока давайте перейдем к поддержке встроенных модулей ES6.

Импортировать все

Одно из самых больших отклонений от синтаксиса модулей CommonJS, когда появились модули ES, - это специальное новое ключевое слово import для доступа к зависимости.

Давайте рассмотрим все доступные типы импорта. В разделе ниже я также рассмотрю параметры экспорта.

Импортировать экспорт по умолчанию

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

Простейший пример импорта только экспорта по умолчанию:

import carTires from './autoParts/tires.js';

В этом первом примере carTires - это экспорт по умолчанию из файла tires.js. Чтобы получить к нему доступ в этом файле, все, что нужно сделать, это объявить экспорт с тем же именем.

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

import carTires, { shineTires } from './autoParts/tires.js';

Деструктурированный экспорт { shineTires } в данном конкретном операторе импорта называется именованным импортом, поскольку он заключен в фигурные скобки и экспортируется из файла tires.js с тем же именем.

Есть еще один вариант, при котором экспорт по умолчанию может быть объединен со всем содержимым модуля с помощью импорта с подстановочными знаками *.

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

import carTires, * as tireOptions from './autoParts/tires.js';

Если была переменная с именем tireType и функция с именем rotateTires() из файла tires.js, импортирующий файл мог получить доступ к ним обоим, вызвав tireOptions.tireType или tireOptions.rotateTires(). Поскольку дополнительные экспорты были импортированы с синтаксисом * as tireOptions, на все экспорты из файла можно ссылаться tireOptions.xyz. Это называется импортом пространства имен.

Примечание. Если вы импортируете экспорт по умолчанию вместе с именованным импортом или импортом пространства имен, имейте в виду, что сначала необходимо объявить экспорт по умолчанию.

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

Содержимое всего модуля: импорт пространства имен

Как я уже упоминал выше, если файл содержит множество отдельных экспортов, к которым вы хотите получить доступ, без необходимости импортировать их все по имени, есть способ сделать это с помощью import * as abc.

Это вставляет abc в текущую область, содержащую все экспорты из модуля в файле, расположенном в /romanAlphabet/letters.js.

Пример одновременного импорта нескольких экспортов из файла:

import * as abc from '/romanAlphabet/letters.js';

Здесь доступ к экспорту означает использование имени модуля (в данном случае abc) в качестве пространства имен. Например, если импортированный выше модуль включает экспорт singMyAbcs(), вы бы назвали его так: abc.singMyAbcs();

Достаточно просто, правда? А теперь поехали.

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

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

Вот еще один пример импорта с одним именем, чтобы было понятнее:

import { apples } from './plants/fruits.js';

Объект apples экспортируется из файла fruits.js либо неявно, потому что весь модуль экспортируется, либо явно с использованием оператора export, а затем вставляется в текущую область.

Этот же синтаксис можно использовать и для множественного импорта.

Множественный экспорт из одного модуля

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

Пример множественного именованного импорта:

import { carrots, potatoes, onions } from './plants/veggies.js';

Все экспорты из veggies.js импортируются в область действия текущего файла и доступны по имени, так же, как одно импортированное значение.

Псевдоним импорта с более удобным именем

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

Что ж, есть решение: присвоить экспорту более удобное имя при импорте. Проверь это.

Пример переименования именованного экспорта для удобства:

import { mySuperCaliFragilisticExpialidociusObject as mySuperObject } from './maryPoppins.js'

Всякий раз, когда вам нужно получить доступ к импортированному значению в текущей области, вы можете просто вызвать его с mySuperObject вместо оригинального, гораздо более длинного экспортированного имени. Как удобно!

Переименовать несколько экспортов во время импорта

Но предположим, что у вас есть несколько объектов экспорта с длинными именами. Можете ли вы переименовать их все при импорте? Что ж, как оказалось, можно.

Так же, как переименование одного экспорта, вы можете переименовать несколько экспортов при импорте.

Пример псевдонима нескольких экспортов:

import { ladyMaryCrawley as ladyMary, ladyEdithCrawley as ladyEdith, ladyCoraCrawley as ladyGrantham } from './downtonAbbeyFamily/ladies.js';

Все дамы из Аббатства Даунтон, импортируемые в текущий файл, имеют довольно длинные заголовки, поэтому для удобства текущая область будет обращаться ко всем с их сокращенными псевдонимами ladyMary, ladyEdith и ladyGrantham.

Только импортные побочные эффекты

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

Пример импорта модуля без импорта чего-либо в частности:

import './helperFunctions.js';

Во всяком случае, этот синтаксис немного напоминает мне способ импорта файлов CSS в отдельные файлы JavaScript.

Динамический импорт

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

Ключевое слово import может быть вызвано как функция для динамического импорта модуля. При таком использовании возвращает обещание.

Пример динамического импорта с синтаксисом обещания:

import ('./waysToTravel.js')
  .then((vehicles) => {
    // do something with planes, trains and automobiles
  });

Его также можно использовать с новым синтаксисом ES6 async / await.

Пример динамического импорта с синтаксисом async / await:

let vehicles = await import('./waysToTravel.js');

Я сам еще не использовал этот тип импорта, но приятно, что они работают над множеством различных способов доступа к объектам и значениям в разных областях с помощью встроенной поддержки модулей.

Экспорт всего остального

Хорошо, мы рассмотрели импортную сторону поддержки модулей ES6. Теперь давайте посмотрим на другую половину уравнения: ключевое слово и синтаксис ES6 export.

Экспорт по умолчанию

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

Примеры экспорта функции, переменной или класса по умолчанию из файла:

// this is how to export a function as a default
export default function getMovies() {
  // fetch some movie data and return it
};
// this is how to export a variable as a default
export default const movie = {
 title: "The Lion King", 
 releaseDate: "July 19, 2019",
 synopsis: "Simba idolizes his father, King Mufasa, and takes to heart his own royal destiny on the plains of Africa."
};
// this is how to export an ES6 class (a React class to be exact)
export default class Movies extends Component {
  // render some movie data in JSX
}

Примечание. Каждый файл JavaScript может иметь только один экспорт по умолчанию. Перед всеми остальными экспортными данными в том же файле будет только ключевое слово export.

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

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

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

Примеры нескольких именованных экспортов в файле:

// this first array of pets is available to be imported into any other file because it has the 'export' keyword
export const myPets = [ "dog", "cat", "guinea pig", "gold fish"];
// mySecretPets can't be directly accessed outside the scope of this file because it lacks the 'export' keyword ahead of it
const mySecretPets = [ "dragon", "griffin", "Loch Ness monster", "Big Foot"]
// nameMyPets can be called in the scope of other files
export function nameMyPets() {
  console.log(myPets, mySecretPets)
}

Как видно из приведенных выше примеров, и переменная myPets, и функция nameMyPets явно экспортируются из файла и, таким образом, могут быть импортированы в другой файл с использованием именованного синтаксиса импорта: import { myPets, nameMyPets } from './pets.js';. Однако другая переменная mySecretPets не экспортируется из файла и, следовательно, не может быть доступна явно за пределами своей исходной области.

Несколько именованных экспортов в одной строке

Есть и другой способ экспортировать несколько значений из одного файла в JavaScript, если вы не хотите вводить export несколько раз во всем файле.

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

Пример одной строки с несколькими именами экспорта:

export { cakes, cookies, makeDessert, makeTea };

Если вы можете представить в приведенном выше примере, что переменные cakes и cookies существуют вместе с функциями makeDessert() и makeTea(), и вы хотите, чтобы все эти значения были доступны в других областях вашей кодовой базы, просто экспортируя их все в конце модуль, вы можете получить к ним доступ где угодно.

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

Экспорт с псевдонимами

Последний вариант экспорта, который я рассмотрю, - это экспорт с псевдонимами, который аналогичен импорту с псевдонимами.

Пример экспорта псевдонимов:

export { dumplings, xiaoLongBao as soupDumplings, bbqPorkBuns, orderDimSum, pickUpSteamerBaskets, pourTea as fillTeaCups };

Так же, как при переименовании именованных экспортов в файле импорта, вы можете фактически переименовывать экспорты по мере их экспорта. В приведенном выше примере я переименовал переменную xiaoLongBao в soupDumplings, а функцию pourTea в fillTeaCups для импорта в любую другую область.

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

И на этом мы завершаем наше путешествие по тонкостям импорта и экспорта с помощью встроенной поддержки модулей ES6!

Заключение

Синтаксис JavaScript ES6 отсутствует уже несколько лет (и с течением времени получает все более широкое распространение), но он внес некоторые революционные изменения в язык, которые были слишком чужими для некоторых разработчиков, чтобы сразу же начать изучать его.

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

Моя цель в этой серии статей - выделить части синтаксиса ES6, который вы используете каждый день, и объяснить, как вы можете использовать эти новые части языка JavaScript для максимального воздействия.

До сих пор не было стандартной поддержки встроенных модулей для всех замечательно полезных модулей библиотеки JavaScript, имеющихся в npm. ES2015 изменил это и представил новые специализированные ключевые слова и множество способов импортировать и экспортировать всевозможные значения JS из одной области видимости в другую с гораздо меньшей суетой и беспокойством, чем раньше. Это полностью изменило правила игры.

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

Предыдущие статьи из этой серии:

Ссылки и дополнительные ресурсы