**Hierarchical Dependency Injection for React With Immutable State Management **
This is a fork of awesome project React-Ioc
- use OOB React Context abilities for propagating changes child compoenents instead of using MobX approach
- Hierarchical Dependency Injection
- Can inject dependencies using React Hooks
- Automatically calls
.dispose()
on created class instances when React unmountProvider
component - Can work without decorators
- Supports lazy service registration with code splitting
- ES6, CommonJS and UMD bundles
- Declarations for TypeScript and Flow
- Rewritten on TypeScript
Differenece with original project React-Ioc
- Instead of MobX using immutable state manager based on Immer
- @provider class decorator or HOC
- toClass binding
- toValue binding
- toFactory binding
- toExisting binding
- @registerIn class decorator
- @inject property decorator
- inject utility function
- useInstance React Hook
import React from 'react';
import { provider, inject, action, ImmutableService, store } from 'react-ioc-immer';
class LoginService extends ImmutableService {
@store() users: User[];
private someVar: number;
@store() complexObject: {
title: string;
items: {}[];
};
}
class PostService {
@inject dataContext: DataContext;
@action
createPost(user: User) {
const post = new Post({ id: uniqueId() });
this.dataContext.posts.set(post.id, post);
return post;
}
}
class PostEditor extends React.Component {
@inject postService: PostService;
render() {
// ...
}
}
@provider(DataContext, PostService)
class App extends React.Component {
render() {
// ...
}
}
HOC (or decorator) that registers dependencies in scope of wrapped component.
import { provider, toClass, toFactory, toValue, toExisting } from 'react-ioc';
@provider(
DataContext, // bind DataContext to self
[IFooService, FooService][(IBarService, toClass(BarService))][(IBazService, toValue({ baz: 123 }))][ // bind IFooService to FooService // bind IBarService to BarService // bind IBazService to static value
// bind MobxStore to factory with dependencies
(MobxStore, toFactory([IFooService, IBarService], (fooService, barService) => MobxStore.create(fooService, barService)))
][
// bind IObsoleteService to already registered IFooService instance
(IObsoleteService, toExisting(IFooService))
]
)
class App extends React.Component {
render() {
// ...
}
}
Providers can be nested:
@provider(DataContext, AuthService)
class App extends React.Component {
render() {
// ...
}
}
@provider(UserService)
class HomePage extends React.Component {
render() {
// ...
}
}
Also Provider
component has static register()
function, for imperative dependencies registration:
// App.jsx
import { provider, toClass } from 'react-ioc';
class App extends React.Component {}
export default provider()(App);
// somewhere else
import App from './App';
App.register(FooService, [BarService, toClass(BarService)]);
Class decorator for lazy service registration in Provider
. Accepts lambda that returns some Proveider
component.
// ./services/LazyService.js
import { registerIn } from 'react-ioc';
import App from '../components/App';
@registerIn(() => App)
export class LazyService {}
// ./components/LazyWidget.jsx
import { inject } from 'react-ioc';
import { LazyService } from '../services/LazyService';
export default class LazyWidget extends React.Component {
@inject lazyService: LazyService;
}
// ./components/App.jsx
import { provider } from 'react-ioc';
const LazyWidget = React.lazy(() => import('./LazyWidget'));
@provider()
export default class App extends React.Component {
render() {
return (
<React.Suspense fallback={<div>Loading...</div>}>
<LazyWidget />
</React.Suspense>
);
}
}
Also, is can accept binding as second argument:
// ./services/LazyService.js
import { registerIn, toClass } from 'react-ioc';
import App from '../components/App';
interface LazyService {
method(): void;
}
class LazyServiceImpl implements LazyService {
// ...
}
@registerIn(() => App, toClass(LazyServiceImpl))
export class LazyService {}
Property decorator for property dependency injection.
Can use dependency types from Reflect Metadata (with TypeScript --emitDecoratorMetadata
):
import { inject } from 'react-ioc';
class FooService {
@inject barService: BarService;
}
class MyComponent extends React.Component {
@inject fooService: FooService;
@inject barService: BarService;
// ...
}
Or manually specified dependencies:
import { inject } from 'react-ioc';
class FooService {
@inject(BarService) barService;
}
class MyComponent extends React.Component {
@inject(FooService) fooService;
@inject(BarService) barService;
// ...
}
If you want to use dependency in React.Component
constructor you should pass context
argument to super()
:
import React from 'react';
import { inject } from 'react-ioc';
class MyComponent extends React.Component {
@inject fooService: FooService;
constructor(props, context) {
super(props, context);
this.fooService.doSomething();
}
}
Utility function for property or constructor dependency injection. Note, that for React Components we should explicitely define static contextType = InjectorContext
(unlike with @inject
decorator).
Property Injection:
import { inject, InjectorContext } from 'react-ioc';
class FooService {
barService = inject(this, BarService);
}
class MyComponent extends React.Component {
fooService = inject(this, FooService);
barService = inject(this, BarService);
static contextType = InjectorContext;
}
Constructor Injection:
import { inject } from 'react-ioc';
class OtherService {
constructor(fooService, barService) {
this.fooService = fooService || inject(this, FooService);
this.barService = barService || inject(this, BarSerivce);
}
}
import { useInstance, useInstances } from 'react-ioc';
const MyButton = props => {
const myService = useInstance(MyService);
return <button onClick={() => myService.doSomething()}>Ok</button>;
};
const MyWidget = props => {
const [fooService, barService] = useInstances(FooService, BarService);
return (
<div>
<MyButton />
</div>
);
};
> npm install --save react-ioc
<script crossorigin src="https://unpkg.com/react-ioc/dist/index.umd.min.js"></script>
const { provider, inject } = window.ReactIoC;