Skip to content

Instantly share code, notes, and snippets.

@gnaeus
Last active September 11, 2019 15:30
Show Gist options
  • Save gnaeus/c65bad1f2b6f7a8d7a8ddf46dc5ba8d0 to your computer and use it in GitHub Desktop.
Save gnaeus/c65bad1f2b6f7a8d7a8ddf46dc5ba8d0 to your computer and use it in GitHub Desktop.
class FirstStore extends Injectable {}
class SecondStore extends Injectable {
firstStore = this.getInstance(FirstStore);
}
class FirstComponent extends ConnectedComponent {
firstStore = this.getInstance(FirstStore);
secondStore = this.getInstance(SecondStore);
render() {
return <div />;
}
}
function SecondComponent() {
const firstStore = useInstance(FirstStore);
const secondStore = useInstance(SecondStore);
return <div />;
}
const container = new Container();
<Provider container={container}>
<FirstComponent />
<SecondComponent />
</Provider>;
import React, { createContext, useContext, Component, ReactNode } from "react";
interface Constructor<T extends Injectable = any> {
new (): T;
}
export abstract class Injectable {
private _container = Container.current;
protected getInstance<T extends Injectable>(constructor: Constructor<T>): T {
return this._container.getInstance(constructor);
}
}
export class Container {
public static current: Container;
private _instancesByType: Map<Constructor, Injectable> = new Map();
public getInstance<T extends Injectable>(constructor: Constructor<T>): T {
if (!(constructor.prototype instanceof Injectable)) {
throw new Error(`${constructor.name} must be derived from Injectable`);
}
let instance = this._instancesByType.get(constructor);
if (!instance) {
const prevContainer = Container.current;
Container.current = this;
try {
instance = new constructor();
} finally {
Container.current = prevContainer;
}
this._instancesByType.set(constructor, instance);
}
return instance as T;
}
}
const Context = createContext<Container>(Container.current);
export function Provider({ container, children }: { container: Container; children: ReactNode }) {
return <Context.Provider value={container}>{children}</Context.Provider>;
}
Provider.displayName = "Provider";
export function useInstance<T extends Injectable>(constructor: Constructor<T>): T {
const container = useContext(Context) || Container.current;
if (!container) {
throw new Error("<Provider> is missing");
}
return container.getInstance(constructor);
}
export abstract class ConnectedComponent<P = {}, S = {}, SS = any> extends Component<P, S, SS> {
static contextType = Context;
public context: Container;
constructor(props: P, context: any) {
super(props, context);
if (arguments.length < 2) {
throw new Error(
`Constructor of ${this.constructor.name} should pass context argument to super(props, context);`
);
}
if (!context) {
context = Container.current;
if (!context) {
throw new Error("<Provider> is missing");
}
this.context = context;
}
}
protected getInstance<T extends Injectable>(constructor: Constructor<T>): T {
return this.context.getInstance(constructor);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment