Я начал свою карьеру в качестве разработчика Angular, поэтому, когда я переключился на React, я начал искать шаблоны, которые были мне знакомы в проектах Angular. В то время не было API контекста реакции, поэтому мне было трудно сделать это возможным на простом JS. Но, наконец, я нашел хорошее решение, подобное приведенному ниже.

import {createContext, useContext, useMemo} from 'react';

const DependencyInjectionContext = createContext();

export const useDI = () => {
  const context = useContext(DependencyInjectionContext);
  if (context === undefined) {
    throw new Error(
      'useDependencyInjection must be used within a DependencyInjectionProvider',
    );
  }
  return context;
};

export const withDI = (Component) => (props) => {
    const dependencies = useDI()
    return <Component {...props} dependencies={dependencies} />;
}

const useUpperDIExists = () => {
  const context = useContext(DependencyInjectionContext);
  return [context !== undefined, context];
};

export const DependencyInjectionProvider = ({children, ...rest}) => {
  const [exists, upperContext] = useUpperDIExists();
  const value = useMemo(
    () => (exists ? {...upperContext, ...rest} : rest),
    [exists, upperContext, rest],
  );
  return (
    <DependencyInjectionContext.Provider value={value}>
      {children}
    </DependencyInjectionContext.Provider>
  );
};

export default DependencyInjectionProvider;

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

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

Прежде чем перейти к хуку useUpperDIExists, давайте заглянем в DependencyInjectionProvider. Основная идея состоит в том, чтобы отрендерить Provider и предоставить реквизиты DependencyInjectionProvider для значения, чтобы все реквизиты были доступны внутри результата хука и реквизитов. Но, как вы видите, я также добавил хук useUpperDIExists, который отвечает за определение того, был ли провайдер уже отрендерен в родительском дереве DOM. Как вы знаете, в большинстве случаев мы не хотим предоставлять все сервисы в один файл и раздувать начальное время загрузки JS. Поэтому хорошей практикой является разделение задач и внедрение некоторых сервисов, когда это необходимо. Допустим, у нас будут сервисы, связанные с пользователем, на всех страницах, но считается, что сервисы продукта доступны только на страницах, связанных с продуктом, где этот хук будет выполнять свою работу, и вы можете сделать дерево чем-то вроде Global DependencyInjectionProvider. Здесь вы будете предоставлять зависимости вашего приложения и Product DependencyInjectionProvider, где вы будете внедрять зависимости, связанные с продуктом, поскольку мы объединяем зависимости. Вы также получите глобальные зависимости. Действительно, вот пример использования в реальном проекте:

import React from 'react';
import {Provider as StoreProvider} from 'react-redux';
import { DependencyInjectionProvider } from './lib/dependencyInjection/index.js';
import Router from './navigation/Router.js';
import {store} from './store';
import theme from './config/theme';
import ApiService from './services/ApiService';

const App = () => {
  return (
    <StoreProvider store={store}>
      <DependencyInjectionProvider store={store} theme={theme} services={ApiService} >
        <Router />
      </DependencyInjectionProvider>
    </StoreProvider>
  );
};
// functional component usage
const Page = () => {
  const {service, theme, store} = useDI();
  return <>...</>
}
//class component usage
class ClassPage extends React.Component {
  constructor(props) {
    super(props);
    this.di = props.dependencies;
  }
  render() {
    const {service, theme, store} = this.dependencies;
    return <>...</>
  }
}

export withDI(ClassPage)

export default App; as you see we can inject anything from props and get it from useDI hook or in case of class component in props. It is similar to angular but instead of constructor we are using hook, but when it comes to the class component we can use withDI HOC and get dependencies form props.dependencies of course you can modify thing to fit to your naming convention using hook and making your own HOC. Alternatively you could also use injecting provider to class component like in react docs https://reactjs.org/docs/context.html 

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