Pull to refresh

Comments 15

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

Да, думаю, неплохо может получиться. Это без спецификации (spec) возможно переименование аргументов при минификации, а если все зависимости идут как ключи объекта, то можно и регуляркой доставать.

За export default существует отдельный котел в аду.

Чем так плох `export default`, что за него так сурово карают в послежизни?

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

Вполне возможно. Но на практике я пока что не сталкивался с необходимостью протягивать имя класса через статические импорты. IDE прекрасно подобное разруливает. PhpStorm, как минимум. Даже с опечатками.

Хорошее объяснение, почему не рекомендуется 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. Тут не столько в терминах исходного кода нужно мыслить, сколько в терминах работающей программы (те же синглтоны).

Спасибо за вопрос, очень полезный оказался, как минимум для меня.

Я прекрасно понимаю о чем Вы говорите. Спасибо за развернутый ответ.

Sign up to leave a comment.

Articles