Внедрение зависимостей (DI) - это системный процесс, присутствующий во многих программных средах: Spring, Flex, Swiz, Angular.

Внедрение зависимостей - это решение, при котором система предоставляет цель 1..n зависимости из внешних источников; вместо того, чтобы требовать, чтобы target сам создавал эти зависимости. Зависимости - это службы, объекты, функции или значения, которые необходимы классу (или фабрике, функции) для выполнения своей функции.

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

Традиционный сценарий

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

Когда мы говорим введено, мы имеем в виду использовано! Лучший способ предоставить внедренные экземпляры - через конструктор (или аргументы функции). Поэтому, когда мы говорим внедренный, мы имеем в виду «переданный как аргумент конструктора».

КонтактыFacade можно реализовать двумя способами:

Без систем DI разработчики, заинтересованные в использовании экземпляра ContactsFacade, должны сначала подготовить экземпляры ContactsService и ContactsStore. Эти экземпляры передаются в качестве аргументов конструкции в ContactsFacade.

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

Этот ручной процесс должен быть хорошо знаком ... и не требуется при наличии надлежащей системы DI.

Внедрение зависимостей в React

  1. React предоставляет контекст как способ передачи данных через дерево компонентов без необходимости передавать реквизиты вручную на каждом уровне. Это позволяет глубоким дочерним компонентам легко искать сервисы (используя Context.Consumer )… сервисы, которые ранее предоставлялись выше в дереве DOM (используя Context.Provider).

IMO, такие конструкции ^ также загромождают разметку HTML и дерево DOM.

2.useContext() - другой подход, который обеспечивает программный поиск служб, зарегистрированных выше вверх иерархии представления.

Ли Уоррик недавно написал статью, в которой обсуждает Маленький грязный секрет Context API. В этой статье подчеркивается, почему не следует использовать контекст.

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

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

  1. Эти подходы не решают цели автоматизации построения и разделения зависимостей.
  2. Эти решения не решают вопросов, как тестировать с помощью макетов и т. Д.

Система истинного внедрения зависимостей (DI) легко решает эти проблемы!

Требования для универсального внедрения зависимостей

Надежная система внедрения зависимостей (DI) предоставляет разработчикам следующие функции.

Вопрос: «Как мы можем создать независимую от фреймворка систему DI, которую можно будет использовать в React или необработанных приложениях JavaScript / TypeScript?»

Для более универсального решения потребуются следующие особенности:

Реализованное здесь решение DI НЕ использует отражение, декораторы или какие-либо другие более сложные функции. DependencyInjector - это легкое автономное решение, которое может работать с любой платформой или приложением ES6 / TypeScript.

Изучение потока DI

Внедрение зависимостей использует процесс поиска, чтобы определить, КАК создать объект. Ключевым аспектом поиска является токен.

Используя концепцию Provider представленных и задокументированных в Angular, мы можем реализовать платформо-независимый подход:

Токен (<token>) - это ключ к процессу DI: это просто объект идентификатора, используемый для запроса и поиска экземпляра кэшированного объекта.

Если кэшированный одноэлементный объект недоступен, то <token> используется для сканирования списка регистраций провайдеров. В таких случаях <token> используется для поиска связанных фабричных методов, которые будут использоваться для создания этого экземпляра.

В большинстве случаев реальным классом является <token>. Однако иногда ссылка на класс невозможна. <token> может быть либо

  • нить
  • Класс
  • InjectionToken

Разработчики должны использовать InjectionToken всякий раз, когда вводимый вами тип не реифицируется (не имеет представления среды выполнения); например, при внедрении интерфейса, вызываемого типа, массива или параметризованного типа.

Разработчикам следует обратить внимание на deps:any[] параметры, которые позволяют каждой фабрике определять последующие зависимости, необходимые для правильного построения. (скоро мы увидим, как это используется)

Индивидуальная настройка DI (для ваших нужд):

Если мы снова рассмотрим дерево зависимостей ContactsFacade:

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

Итак, как можно легко настроить отношения построения для программного DI? Нам нужна система DI, которая:

  • можно легко использовать на любом уровне просмотра
  • поддерживает одиночные экземпляры
  • поддерживает переопределение (не одноэлементные) экземпляры
  • поддерживает несколько поставщиков DI

Мы можем легко создать собственный injector экземпляр, используя makeInjector() factory. Ниже мы зарегистрировали набор (1..n) конфигураций Contact Provider для создания настраиваемого контактного инжектора.

Лучшая часть кастомного инжектора - это его функция по требованию (ленивая). Когда разработчик использует injector.get(<token>), сначала проверяется реестр кеша, чтобы узнать, существует ли экземпляр. Если нет, только тогда будут вызваны необходимые фабрики для создания и кэширования экземпляра.

DI с компонентами React

Использование наших пользовательских инжекторов на уровне React View тривиально: мы импортируем, а затем используем синтаксис injector.get(<token>)!

С точки зрения компонента представления "Список контактов ^", представление не касается "как", "когда" или "где". ContactsService был создан или кэширован.

Вместо этого компонент представления интересует только ИСПОЛЬЗОВАНИЕ API ContactsService.

Использование useInjectorHook()

Наше использование функций DI на уровне представления можно упростить еще больше, если мы создадим настраиваемую ловушку Injection с использованием useInjectorHook() API (см. Строку 11):

Теперь - на уровне представления - код DI очень прост (см. Строку 14):

Простое тестирование с помощью DI + Mocks

Рассмотрим дополнительное требование для изолированного тестирования компонента представления. Это означает, что при тестировании потребуется внедрить поддельную или фиктивную службу ContactsService в компонент представления ContactsList.

С DI очень легко заменить «настоящего» провайдера на «фиктивного» провайдера:

При этой замене в строках 10–13 выше существующие конфигурации поставщика (определенные в ./contacts) будут переопределены.

В дальнейшем запросы на внедренный экземпляр ContactsService будут доставить только фиктивный ContactsService.

Соображения

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

  • makeInjector() и
  • import {injector} from ‘….’

Эти два (2) ручных шага необходимы, поскольку система DI не была спроектирована в React, как в Angular!

К счастью, это решение DI, которое можно использовать в любом компоненте представления, с любой глубиной просмотра или в любом сервисе (без просмотра)! Ручная часть - это небольшое вложение усилий, которое дает огромную рентабельность инвестиций с истинным внедрением зависимостей.

Использование библиотеки

Установите функции DI в свой проект, используя:

npm install @mindspace-io/utils --save

Чтобы настроить свой собственный инжектор, просто импортируйте функции DI:

import { makeInjector, DependendecyInjector, InjectionToken } from ‘@mindspace-io/utils';

Не забудьте создать собственный хук, чтобы упростить поиск DI!