Last active
September 11, 2019 15:30
-
-
Save gnaeus/c65bad1f2b6f7a8d7a8ddf46dc5ba8d0 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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>; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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