Dependency Injection (DI)
Dependency Injection (DI) allows you to split React components into separate versions and comfortably switch them in the project whenever needed, e.g., to make a specific bundle.
DI package helps to solve similar tasks with minimum effort:
decouple desktop and mobile versions of a component
implement an experimental version of a component alongside the common one
Install
npm i @bem-react/di -S
Quick start
Note! This example uses ClassName package.
E.g., for a structure like this:
Components/
Header/
Header@desktop.tsx
Header@mobile.tsx
Footer/
Footer@desktop.tsx
Footer@mobile.tsx
App.tsx
First, create two files that define two versions of the App and use different sets of components: App@desktop.tsx
and App@mobile.tsx
. Put them near App.tsx
.
In each App version (App@desktop.tsx
and App@mobile.tsx
) we should define which components should be used.
Three steps to do this:
Create a registry with a particular id:
const registry = new Registry({ id: cnApp() });
Register all the needed components versions under a descriptive key (keys, describing similar components, should be the same across all the versions):
registry.set(cnHeader(), Header); registry.set(cnFooter(), Footer);
Export the App version with its registry of components:
export const AppNewVersion = withRegistry(registry)(AppCommon);
The files should look like this:
1. In App@desktop.tsx
import { cn } from '@bem-react/classname';
import { Registry, withRegistry } from '@bem-react/di';
import { App as AppCommon } from './App';
import { Footer } from './Components/Footer/Footer@desktop';
import { Header } from './Components/Header/Header@desktop';
const cnApp = cn('App');
const cnHeader = cn('Header');
const cnFooter = cn('Footer');
const registry = new Registry({ id: cnApp() });
registry.set(cnHeader(), Header);
registry.set(cnFooter(), Footer);
export const AppDesktop = withRegistry(registry)(AppCommon);
2. In App@mobile.tsx
import { cn } from '@bem-react/classname';
import { Registry, withRegistry } from '@bem-react/di';
import { App as AppCommon } from './App';
import { Footer } from './Components/Footer/Footer@mobile';
import { Header } from './Components/Header/Header@mobile';
const cnApp = cn('App');
const cnHeader = cn('Header');
const cnFooter = cn('Footer');
const registry = new Registry({ id: cnApp() });
registry.set(cnHeader(), Header);
registry.set(cnFooter(), Footer);
export const AppMobile = withRegistry(registry)(AppCommon);
Time to use these versions in your app dynamically!
If in App.tsx
your dependencies were static before
import { cn } from '@bem-react/classname';
import { Header } from './Components/Header/Header';
import { Footer } from './Components/Footer/Footer';
const cnPage = cn('Page');
export const App: React.SFC = () => (
<div className={ cnPage() }>
<Header />
<Content />
<Footer />
</div>
);
Now the dependencies can be injected based on the currently used registry
import { cn } from '@bem-react/classname';
import { RegistryConsumer } from '@bem-react/di';
// No Header or Footer imports
const cnApp = cn('App');
const cnPage = cn('Page');
const cnHeader = cn('Header');
const cnFooter = cn('Footer');
export const App: React.SFC = () => (
<RegistryConsumer>
{registries => {
// Get registry with components
const registry = registries[cnApp()];
// Get the needed version of the component based on registry
const Header = registry.get(cnHeader());
const Footer = registry.get(cnFooter());
return(
<div className={ cnPage() }>
<Header />
<Content />
<Footer />
</div>
);
}}
</RegistryConsumer>
);
export default App;
So you could use different versions of your app e.g. for conditional rendering on your server side or to create separate bundles
import { AppDesktop } from './path-to/App@desktop';
import { AppMobile } from './path-to/App@mobile';