
Комментарии 15
Спасибо, любопытный подход. Имхо, обмазывать каждый класс рукописными проверками на отсутствие зависимостей и кидание специальным образом помеченного исключения, а потом крутить это в цикле - некрасивое решение. Вместо этого я бы взял строковое представление функции и достал оттуда регуляркой определение аргументов - сразу получаем полный список зависимостей и избавляемся от бойлерплейта.
За export default существует отдельный котел в аду.
Чем так плох `export default`, что за него так сурово карают в послежизни?
Я бы не сказал что он настолько плох, но проблема в том, что для одной сущности можно легко и неочевидно завести несколько имен, включая опечатки.
Хорошее объяснение, почему не рекомендуется export default есть в Google TypeScript Style Guide (раздел "Экспорт") (хоть и TS, но также подходит и к JS).
Пример поведения, который не всегда является желанным:
bar.ts :
export default class Foo { ... }
custom-module.ts:
import Foo from './bar'; // Валидно.
import Bar from './bar'; // Также валидно.
Потом при ревью, отладке и пр., в голове приходится держать, что Foo === Bar, что не всегда очевидно и может приводить к неприятным неожиданностям в коде.
Спасибо за пояснение. Но примерно такие же проблемы могут возникнуть и без default'а:
// ./bar.js
export class Foo {}import {Foo} from './bar.js';
import {Foo as Bar} from './bar.js';
const f1 = new Foo();
const b1 = new Bar();IMHO, иметь один класс в одном модуле - это очень хорошая практика для бэка, судя по Java/PHP. Да и вообще SOLID'но (single-responsibility principle). А раз так, то больше одного экспорта и не особо-то нужно.
По рукам, на мой взгляд, нужно бить не за то, что кто-то что-то по default'у экспортирует, а как раз вот за такое использование (если оно ни чем не обусловлено):
import Foo from './bar'; // Валидно.
import Bar from './bar'; // Также валидно.впрочем, как и за:
import {Foo} from './bar.js';
import {Foo as Bar} from './bar.js';Обратите внимание пожалуйста на разницу этих двух примеров:
import Bar from './bar';
и
import {Foo as Bar} from './bar';
Несколько моментов:
В первом случае импортируется "что-то", что далее будет именоваться как
Barи нет никаких гарантий, что это будет именно классFoo. Во втором случае, вы явно импортируете конкретно классFooи при помощи оператораasчетко даете понять ревьюверу, что вам необходим именно классFoo, но в данном модуле он будет именоваться как классBar.Далее, если взглянуть на первый вариант, будет ли очевидно без помощи IDE, что для того, чтобы понять, что такое
Barи какое у него назначение, вам нужно будет в справке прочитать раздел про классFooмодуля bar. Во втором случае, вcе гораздо очевидней (классFoo, модульbar);В JS, где с контролем типов и так все грустно (привет нарастающей популярности TypeScript), встроенная проверка того, что импортируется именно класс
Fooбудет не лишней. Если кто-нибудь случайно или специально сделает какой-то другой класс экспортируемым по умолчанию, то во втором случае вы сразу получите ошибку импорта. А вот в первом случае вы получите ошибку об отсутствии в объекте каких-то методов или свойств и не сразу догадаетесь, что это связано именно с некорректным импортом.
Код обычно читают намного чаще, чем пишут, поэтому чем очевидней код при чтении, тем лучше. В любом случае, стоит придерживаться соглашений по стилю кода (в т.ч. по экспортам/импортам), которые уже приняты в проекте.
В любом случае, стоит придерживаться соглашений по стилю кода (в т.ч. по экспортам/импортам), которые уже приняты в проекте.
Согласен.
За export default существует отдельный котел в аду.
Т.е., это какие-то внутрипроектные соглашения, но вынесенные глобальный на уровень.
Так же, как я у себя в примере взял за основу, что любой es6-модуль по-умолчанию экспортирует фабричную функцию:
export default async function Factory() {
return /* что-угодно */;
};Но только не грозил никому отдельным котлом в постмортеме за их неприменение ;)
Кстати, в JS возможно что угодно экспортировать, как Foo, не обязательно классы:
const Bar = {
name: 'Bar'
};
export {
Bar as Foo
};И будет ни чуть не лучше и не хуже, чем с default:

Вот лично для меня, статья выглядит как какая-то поделка новичка. Если вы хотите раскрыть довольно сложную тему, то нужно показывать куски кода близкие к production-ready. То есть должен быть ts, мало-мальски адекватный код стаил, продуманная система типов. Про паттерны порождения объектов и di написано много статей как и на многие другие темы. А вот качественных шаблонов для той или иной задачи единицы, как раз их я и ожидал прочитав заголовок данной статьи.
Не согласен с вашей точкой зрения.
код в туториале должен быть понятен, а не близок к production ready.
Продуманная система типов в задаче уровня "Hello World!" не нужна, нужна демонстрация базовых принципов.
"Качественный шаблон" для решения задачи IoC и внедрения зависимостей в ES6+ ниже, но ваша вера в Типы Скриптовы не позволит вам его увидеть:
export default async function (spec) {
return /* что-угодно */;
};Ну и вы сами виноваты в своих неоправдавшихся ожиданиях - статья изначально называлась "Внедрение зависимостей в ES6+ 'на пальцах'". В ES6+, а не в TS. В TS, как и в Java, для этой цели зачастую используются аннотации. Ибо транспиляция позволяет.
Спасибо за публикацию. Вопрос по поддержке типизации, если уместен.
Подскажите, пожалуйста, а Вы пробрасываете типы из динамически загружаемого модуля через контейнер в другие es-модули?
Я не пробрасываю типы (вернее, классы/объекты/функции), я использую их напрямую там, где в них есть необходимость. Контейнер знает, каким образом получить доступ к каждому es-модулю проекта и его содержимому (экспорту). Он может напрямую создавать требуемые объекты, без посредников. В данном примере это описано в пункте "7. Карта зависимостей" в виде переменной map, где зависимости между идентификатором зависимости и местом расположения его исходников прописано вручную.
В реальных проектах идентификаторы зависимостей выглядят более кучеряво и, как правило, основаны на полном имени класса, который создаёт нужную для внедрения зависимость (например com.vendor.project.module.BaseClass - в Java или /Vendor/Project/Module/BaseClass в PHP). Так как идентификатор зависимости - это строка, то лишь от фантазии разработчика зависит, какие правила он использует для преобразования строки в:
путь к файлу с исходниками для импорта;
определение нужного экспорта внутри es6-модуля;
флаг создания нового экземпляра зависимости (transient) или передачи уже существующего (singleton);
флаг добавления адаптора (wrapper'а) к создаваемой зависимости;
Зависимость вполне может выглядеть, как URL:
@vendor/package/esmodule.mjs#exportName?singleton&scope=request
Зависимость - это не просто тип (уровень исходного кода), это объект, существующий в runtime. Тут не столько в терминах исходного кода нужно мыслить, сколько в терминах работающей программы (те же синглтоны).
Спасибо за вопрос, очень полезный оказался, как минимум для меня.
Внедрение зависимостей в ES6+ «на пальцах»