Last active
June 3, 2019 14:36
-
-
Save hediet/4fe250623aa6c0ebf3b93ea9c3b567d0 to your computer and use it in GitHub Desktop.
React Dependency Injection
This file contains hidden or 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 * as React from "react"; | |
import { Container } from "inversify"; | |
export function ref<T>(name: string): { T: T; id: string } { | |
return { | |
T: null!, | |
id: name | |
}; | |
} | |
type Simplify<T> = { [TKey in keyof T]: T[TKey] }; | |
export type Ctor<T> = new (...args: any[]) => T; | |
const ContainerContext = React.createContext<Container | undefined>(undefined); | |
export const ContainerProvider = ContainerContext.Provider; | |
const containerProp = "prop123"; | |
export function withModel<TProps, TModel>( | |
model: new (props: TProps, ...rest: any[]) => TModel, | |
Component: Ctor<React.Component<{ model: TModel }>> | |
): React.FunctionComponent<TProps> & { | |
Wrapped: Ctor<React.Component<{ model: Simplify<TModel> }>>; | |
} { | |
const Inner = class extends React.Component< | |
TProps & { [containerProp]: Container } | |
> { | |
private model: TModel; | |
constructor(props: TProps & { [containerProp]: Container }) { | |
super(props); | |
const container = props[containerProp]; | |
const innerContainer = new Container(); | |
innerContainer.parent = container; | |
innerContainer.bind("props").toConstantValue(props); | |
innerContainer.bind(model).toSelf(); | |
this.model = innerContainer.get(model); | |
} | |
componentWillUnmount() { | |
if ("dispose" in this.model) { | |
(this.model as any).dispose(); | |
} | |
} | |
render() { | |
return <Component model={this.model} />; | |
} | |
}; | |
const resultComponent = function(props: TProps) { | |
return ( | |
<ContainerContext.Consumer> | |
{val => { | |
if (!val) { | |
throw new Error("Container must be provided"); | |
} | |
const newProps = { | |
[containerProp]: val, | |
...props | |
}; | |
return <Inner {...newProps} />; | |
}} | |
</ContainerContext.Consumer> | |
); | |
}; | |
resultComponent.Wrapped = Component; | |
return resultComponent; | |
} |
This file contains hidden or 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 "reflect-metadata"; | |
import * as React from "react"; | |
import * as ReactDom from "react-dom"; | |
import { Container, inject, injectable } from "inversify"; | |
import { ref, withModel, ContainerProvider } from "./helper"; | |
// services | |
interface IUserService { | |
currentUser: string; | |
} | |
@injectable() | |
class UserService implements IUserService { | |
get currentUser(): string { | |
return "test"; | |
} | |
} | |
const userServiceRef = ref<UserService>("UserService"); | |
// gui | |
@injectable() | |
class UserComponentModel { | |
constructor( | |
@inject("props") | |
private readonly props: { uppercase: boolean }, | |
@inject(userServiceRef.id) | |
private readonly userService: typeof userServiceRef.T | |
) {} | |
public get currentUser(): string { | |
const usr = this.userService.currentUser; | |
if (this.props.uppercase) { | |
return usr.toUpperCase(); | |
} | |
return usr; | |
} | |
private dispose() { | |
console.log("On unmount"); | |
} | |
} | |
const UserComponent = withModel( | |
UserComponentModel, | |
class extends React.Component<{ model: UserComponentModel }> { | |
render() { | |
const model = this.props.model; | |
return <div>Cur user: {model.currentUser}</div>; | |
} | |
} | |
); | |
class Gui extends React.Component { | |
private readonly container = new Container(); | |
constructor(props: {}) { | |
super(props); | |
this.container.bind(userServiceRef.id).to(UserService); | |
} | |
render() { | |
return ( | |
<ContainerProvider value={this.container}> | |
<UserComponent uppercase /> | |
<UserComponent.Wrapped model={{ currentUser: "test" }} /> | |
</ContainerProvider> | |
); | |
} | |
} | |
const main = document.createElement("div"); | |
document.body.append(main); | |
ReactDom.render(<Gui />, main); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment