XML не очень хорошо мержится, это да, например resx у нас все-время конфликтит. Но проблемы с проектными файлами у нас не такие большие, как Вы описываете. Может Вы работали со старыми версиями студии, мы ведем активную разработку в разных ветках, и мерж проектных файлов не вызывает такой боли, чтобы думать об отказе от них.
Конструктор в обобщенном понимании — это тот интерфейс, через который осуществляется доступ к сущности. Именно так следует понимать слово "конструктор" в концепции "constructor injection", потому как смысл этой концепции — поместить зависимости там, где их невозможно проигнорировать, забыть, где о них подскажет компилятор. В ООП — это действительно конструктор класса (хотя не всегда, при использовании фабрик и других подобных паттернов объекты могут создаваться не через конструктор, и там тоже нужно подходить творчески к этому термину). В React роль конструктора выполняют props, так как не передав props мы не можем использовать компонент. Использование constructor injection в react должно подразумевать именно использование props.
С учетом diInject у Вас получился Service Locator на самом деле, а не Constructor Injection. Как бы на уровне необернутого компонента вроде как есть Constructor Injection, но Вы все компоненты оборачиваете, и обернутые они уже просто берут что найдут в контейнере, и никто этот процесс не контролирует.
Я так понимаю, что никакой проверки типа у второго параметра diInject нет? То есть, TypeScript не проверяет, что booksListModel: ListModel<IBook>; в объявлении Props и booksListModel: new Dependence(constants.booksListModelName), должны соответствовать друг другу, или хотя бы, что все зависимости переданы.
Увидел много кода с классами, и так и не увидел кода компонента React, который использует данный подход. В статье есть только такое:
Теперь перейдем к компоненту, который будет получать из нашего контейнера необходимые данные в виде пропсов.
И все, дальше сбивчиво текстом объясняется, но я так и не понял, каким образом компонент объявляет свои зависимости, и каким образом это объявление читает HOC. Можно привести код хотя бы одного такого компонента? Про использование Inversify с обычным классами можно почитать в документации Inversify, я надеялся увидеть то, что заявлено в заголовке статьи.
Вот только он дергает страницу резко и не всегда по делу. Например, элемент может быть уже виден, а страница все-равно дернется так что пользователь потеряет контекст.
У него по спеке есть всякие няшные параметры (вроде плавной прокрутки), но их мало кто поддерживает.
Главное, чтобы в Вашем API были нормальные сигнатуры функций. То есть, возвращаемое значение — модель, а не ActionResult какой-нибудь. Тогда можно рефлекшеном пробежаться и сгенерировать схему как в статье.
Когда делаешь классы наследники ObjectGraphType подразумевается, что вся схема известна на этапе компиляции. Здесь я генерирую схему на основе динамических метаданных Docsvision.
Вы однобоко рассматриваете внедрение зависимостей только с точки зрения удобства тестирования. Есть еще повторное использование кода, причем возможно не автором кода. В этом юзкейсе только явные зависимости в интерфейсе и ничего другого быть не может.
Вот допустим, разместили вы на гитхабе компонент, и хотите сделать там инверсию зависимостей. Использование чего-то вроде inversify тут было бы странным, а вот описанный мной подход очень подходит.
Кстати как бороться с неуникальностью ключей в services?
Ключ в этой системе должен быть уникальным. Допустим, в Service Locator сервисы получаются по имени интерфейса — соответственно, имя интерфейса тоже должно быть уникальным. Согласен, что есть потенциальная проблема.
Экспериментируя с DI
А как потом использовать TodosView? Как передать ему другую реализацию сервиса? И где он создает для себя свои зависимости (где создается TodosRespository)?
interface MyComponentProps {
services: $Logger;
}
class MyComponent extends React.Component<MyComponentProps, {}> {
defaultServices = {
logger: new Logger();
};
services: $Logger;
constructor(props) {
this.services = applyDefaultServices(props.services, this.defaultServices);
}
}
function applyDefaultServices(services, defaultServices) {
for (let prop in defaultServices) {
if (!services[prop]) {
services[prop] = defaultServices[prop];
}
}
}
Выглядит вполне прилично. Еще там выше предлагали вариант, как можно объявлять services родительского компонента через services: $Logger & SomeChildComponentProps['services'].
генерацией метаданных из сигнатур конструкторов и выстраиванием на их основе DI
Можете подробнее? Пока я вижу в Ваших словах все тот же Service Locator. Если мы не передаем явно каждый раз зависимости, то значит компонент можно создать не передав ему нужные зависимости. В этом вся соль constructor injection — он не позволяет такой ситуации возникнуть на уровне компилятора.
Первые 2 пункта в сторону constructor injection как такового. Если их решить, то получится Service Locator…
Все зависимости по-умолчанию жесткие.
У нас есть хитрая система, которая позволяет писать так:
services: $Logger = App.Instance;
Об этом будет статья чуть позже.
Как быть с зависимостями сервисов в compostion root?
Вот пример из статьи:
let logger = new Logger();
export var services = {
logger: logger,
localStorage: new LocalStorage(),
heroService: new HeroService({ logger }) // Обратите внимание!
};
Тогда мы опять вернемся к Service Locator. Проблема с контекстом в том, что он не представлен в публичном интерфейсе компонента. То есть, пользователь может создать компонент, не передав ему необходимые сервисы, и узнает об ошибке только во время выполнения. При этом, выяснить какие компоненту нужны сервисы можно только через документацию, изучение его кода или методом проб и ошибок.
Не нахожу в своих csproj чего-то вроде:
XML не очень хорошо мержится, это да, например resx у нас все-время конфликтит. Но проблемы с проектными файлами у нас не такие большие, как Вы описываете. Может Вы работали со старыми версиями студии, мы ведем активную разработку в разных ветках, и мерж проектных файлов не вызывает такой боли, чтобы думать об отказе от них.
Конструктор в обобщенном понимании — это тот интерфейс, через который осуществляется доступ к сущности. Именно так следует понимать слово "конструктор" в концепции "constructor injection", потому как смысл этой концепции — поместить зависимости там, где их невозможно проигнорировать, забыть, где о них подскажет компилятор. В ООП — это действительно конструктор класса (хотя не всегда, при использовании фабрик и других подобных паттернов объекты могут создаваться не через конструктор, и там тоже нужно подходить творчески к этому термину). В React роль конструктора выполняют props, так как не передав props мы не можем использовать компонент. Использование constructor injection в react должно подразумевать именно использование props.
Возможен
С учетом diInject у Вас получился Service Locator на самом деле, а не Constructor Injection. Как бы на уровне необернутого компонента вроде как есть Constructor Injection, но Вы все компоненты оборачиваете, и обернутые они уже просто берут что найдут в контейнере, и никто этот процесс не контролирует.
Я так понимаю, что никакой проверки типа у второго параметра diInject нет? То есть, TypeScript не проверяет, что
booksListModel: ListModel<IBook>;
в объявлении Props иbooksListModel: new Dependence(constants.booksListModelName),
должны соответствовать друг другу, или хотя бы, что все зависимости переданы.Увидел много кода с классами, и так и не увидел кода компонента React, который использует данный подход. В статье есть только такое:
И все, дальше сбивчиво текстом объясняется, но я так и не понял, каким образом компонент объявляет свои зависимости, и каким образом это объявление читает HOC. Можно привести код хотя бы одного такого компонента? Про использование Inversify с обычным классами можно почитать в документации Inversify, я надеялся увидеть то, что заявлено в заголовке статьи.
Не, пока не пробовали.
Вот только он дергает страницу резко и не всегда по делу. Например, элемент может быть уже виден, а страница все-равно дернется так что пользователь потеряет контекст.
У него по спеке есть всякие няшные параметры (вроде плавной прокрутки), но их мало кто поддерживает.
Главное, чтобы в Вашем API были нормальные сигнатуры функций. То есть, возвращаемое значение — модель, а не ActionResult какой-нибудь. Тогда можно рефлекшеном пробежаться и сгенерировать схему как в статье.
Когда делаешь классы наследники ObjectGraphType подразумевается, что вся схема известна на этапе компиляции. Здесь я генерирую схему на основе динамических метаданных Docsvision.
Ух ты, спасибо, как-то не нашел. Там правда в январе этого года первый коммит, молодая библиотека.
Это какая-то фантастика. Аплодирую стоя.
В typedin у меня еще лучше — ключем выступает ссылка на конструктор класса. Само собой, с ограничением, что нельзя использовать интерфейсы.
Вы однобоко рассматриваете внедрение зависимостей только с точки зрения удобства тестирования. Есть еще повторное использование кода, причем возможно не автором кода. В этом юзкейсе только явные зависимости в интерфейсе и ничего другого быть не может.
Вот допустим, разместили вы на гитхабе компонент, и хотите сделать там инверсию зависимостей. Использование чего-то вроде inversify тут было бы странным, а вот описанный мной подход очень подходит.
Да, клонировать там будет правильнее.
Ключ в этой системе должен быть уникальным. Допустим, в Service Locator сервисы получаются по имени интерфейса — соответственно, имя интерфейса тоже должно быть уникальным. Согласен, что есть потенциальная проблема.
А как потом использовать TodosView? Как передать ему другую реализацию сервиса? И где он создает для себя свои зависимости (где создается TodosRespository)?
Хм, как Вам такой вариант:
Выглядит вполне прилично. Еще там выше предлагали вариант, как можно объявлять services родительского компонента через
services: $Logger & SomeChildComponentProps['services']
.Можете подробнее? Пока я вижу в Ваших словах все тот же Service Locator. Если мы не передаем явно каждый раз зависимости, то значит компонент можно создать не передав ему нужные зависимости. В этом вся соль constructor injection — он не позволяет такой ситуации возникнуть на уровне компилятора.
Чуть выше уже ответил staticlab на аналогичный вопрос.
Первые 2 пункта в сторону constructor injection как такового. Если их решить, то получится Service Locator…
У нас есть хитрая система, которая позволяет писать так:
Об этом будет статья чуть позже.
Вот пример из статьи:
Тогда мы опять вернемся к Service Locator. Проблема с контекстом в том, что он не представлен в публичном интерфейсе компонента. То есть, пользователь может создать компонент, не передав ему необходимые сервисы, и узнает об ошибке только во время выполнения. При этом, выяснить какие компоненту нужны сервисы можно только через документацию, изучение его кода или методом проб и ошибок.
Не знал что так можно, спасибо. Отличное решение.