react-magnetic-di
Version:
Context driven dependency injection
86 lines (85 loc) • 3.29 kB
JavaScript
function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
import React, { forwardRef, Component } from 'react';
import PropTypes from 'prop-types';
import { diRegistry } from './constants';
import { Context } from './context';
import { addInjectableToMap, getDisplayName, findInjectable } from './utils';
import { globalDi } from './global';
export class DiProvider extends Component {
constructor(...args) {
super(...args);
this.value = undefined;
}
componentDidCatch(err) {
globalDi._remove(this.props.use);
throw err;
}
componentWillUnmount() {
globalDi._remove(this.props.use);
}
getValue() {
if (this.value) return this.value;
const {
use,
target,
global
} = this.props;
const {
getDependencies
} = this.context;
// create a map of dependency real -> replacements for fast lookup
const replacementMap = use.reduce((acc, inj) => {
addInjectableToMap(acc, inj);
return acc;
}, new Map());
// supports global di if needed
globalDi._fromProvider(use, {
global
});
// support single or multiple targets
const targets = target && new WeakSet(Array.isArray(target) ? target : [target]);
this.value = {
getDependencies(realDeps, targetChild) {
// First we collect dependencies from parent provider(s) (if any)
const dependencies = getDependencies(realDeps, targetChild);
// If no target or target is in the array of targets, map use
if (!targets || targets.has(targetChild)) {
for (let i = 0; i < dependencies.length; i++) {
// dep can be either the original or a replacement
// if another provider at the top has already swapped it
// so we check if here we need to inject a different one
// or return the original / parent replacement
const dep = dependencies[i];
const real = diRegistry.has(dep) ? diRegistry.get(dep).from : dep;
const replacedInj = findInjectable(replacementMap, real, targetChild);
if (replacedInj) dependencies[i] = replacedInj.value;
}
}
return dependencies;
}
};
return this.value;
}
render() {
return /*#__PURE__*/React.createElement(Context.Provider, {
value: this.getValue()
}, this.props.children);
}
}
DiProvider.contextType = Context;
DiProvider.propTypes = {
children: PropTypes.oneOfType([PropTypes.func, PropTypes.node]),
global: PropTypes.bool,
target: PropTypes.oneOfType([PropTypes.func, PropTypes.arrayOf(PropTypes.func)]),
use: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object])).isRequired
};
export function withDi(Comp, deps, target = null) {
const WrappedComponent = /*#__PURE__*/forwardRef((props, ref) => /*#__PURE__*/React.createElement(DiProvider, {
use: deps,
target: target
}, /*#__PURE__*/React.createElement(Comp, _extends({
ref: ref
}, props))));
WrappedComponent.displayName = getDisplayName(Comp, 'withDi');
return WrappedComponent;
}