TypeScript 的简单 IOC 容器

导言
用于TypeScript的IoC(倒置控制)容器.
它背后的想法是创建一个简单的容器,可以注册和解决类和函数的依赖性而无需依赖反射元数据.
它使用平版Script代码,因此可以添加到任何项目上而无需额外的依赖.
它也在"Next.js"中软件和"节点"和"边缘跑道"中工作.
安装
npm i @evyweb/ioctopus
如何使用
要使用章鱼,需要创建容器并绑起自属物. 为此,您需要为您想要注册的每个受抚养人创建ID.
这个ID,我们称之为"注入符",可以是字符串或符号. (请注意,你必须一致:要么总是使用字符串来约束和解决依赖关系,要么总是使用符号;你不能混合它们. )
然后可以将依赖绑定在一个值,一个函数,一个类,一个工厂,一个更高级的函数,或者一个曲折的函数上.
安全类型登记(建议)
说明: 采用安全型登记册是改进类型安全和开发者经验的建议办法.
您可以定义一个注册类型或接口,以获取您依赖性注射密钥及其相应类型的全部类型安全性. 这就不需要在解决依赖性时进行人工铸造.
容器还验证您提供的依赖性与您的类或函数所期望的匹配。 TypeScript 将在编译时捕获错误,例如省略所需的依赖性或提供错误的依赖类型.
设立书记官处
定义将您的注射符映射到想要的类型的类型或界面 :
// Using strings as injection tokens
type MyRegistry = {
'USER_SERVICE': UserService;
//...
}
// Or using symbols
const USER_SERVICE = Symbol('USER_SERVICE');
type MyRegistry = {
[USER_SERVICE]: UserService;
//...
}
创建容器
用您的注册类型创建您的容器 :
import { createContainer } from '@evyweb/ioctopus';
// Create a type-safe container (Recommended)
const container = createContainer<MyRegistry>();
// Old way, without type safety
const container = createContainer();
注册依赖关系
初级
你可以注册原始人.
// using strings
container.bind('DEP1').toValue('dependency1');
container.bind('DEP2').toValue(42);
// using symbols
container.bind(DI.DEP1).toValue('dependency1');
container.bind(DI.DEP2).toValue(42);
职能
- 无需依赖即可注册函数 :
const sayHelloWorld = () => console.log('Hello World');
// using strings
container.bind('SIMPLE_FUNCTION').toFunction(sayHelloWorld);
// or using symbols
container.bind(DI.SIMPLE_FUNCTION).toFunction(sayHelloWorld);
咖喱鱼
- 您可以使用 Currying (1级 Currying) 注册依赖函数 :
const myFunction = (dep1: string, dep2: number) => (name: string) => console.log(`${dep1} ${dep2} ${name}`);
// using strings
container.bind('CURRIED_FUNCTION_WITH_DEPENDENCIES')
.toCurry(myFunction, ['DEP1', 'DEP2']);
// or using symbols
container.bind(DI.CURRIED_FUNCTION_WITH_DEPENDENCIES)
.toCurry(myFunction, [DI.DEP1, DI.DEP2]);
- 也可以使用依赖对象:
interface Dependencies {
dep1: string,
dep2: number
}
const myFunction = (dependencies: Dependencies) => (name: string) => console.log(`${dependencies.dep1} ${dependencies.dep2} ${name}`);
// The dependencies will be listed in an object in the second parameter
// using strings
container.bind('CURRIED_FUNCTION_WITH_DEPENDENCIES')
.toCurry(myFunction, {dep1: 'DEP1', dep2: 'DEP2'});
// or using symbols
container.bind(DI.CURRIED_FUNCTION_WITH_DEPENDENCIES)
.toCurry(myFunction, {dep1: DI.DEP1, dep2: DI.DEP2});
上级职能
您也可以通过使用更高排序函数来注册依赖函数 :
const MyServiceWithDependencies = (dep1: string, dep2: number): MyServiceWithDependenciesInterface => {
return {
runTask: () => {
// Do something with dep1 and dep2
}
};
};
// The dependencies will be listed in an array in the second parameter
// using strings
container.bind('HIGHER_ORDER_FUNCTION_WITH_DEPENDENCIES')
.toHigherOrderFunction(MyServiceWithDependencies, ['DEP1', 'DEP2']);
// or using symbols
container.bind(DI.HIGHER_ORDER_FUNCTION_WITH_DEPENDENCIES)
.toHigherOrderFunction(MyServiceWithDependencies, [DI.DEP1, DI.DEP2]);
但如果你喜欢,也可以使用依赖对象:
interface Dependencies {
dep1: string,
dep2: number
}
const MyService = (dependencies: Dependencies): MyServiceInterface => {
return {
runTask: () => {
// Do something with dependencies.dep1 and dependencies.dep2
}
};
};
// The dependencies will be listed in an object in the second parameter
// using strings
container.bind('HIGHER_ORDER_FUNCTION_WITH_DEPENDENCIES')
.toHigherOrderFunction(MyService, {dep1: 'DEP1', dep2: 'DEP2'});
// or using symbols
container.bind(DI.HIGHER_ORDER_FUNCTION_WITH_DEPENDENCIES)
.toHigherOrderFunction(MyService, {dep1: DI.DEP1, dep2: DI.DEP2});
工厂
对于更复杂的案件,您可以注册工厂.
// using strings
container.bind('MY_USE_CASE').toFactory(() => {
// Do something before creating the instance
// Then return the instance
return MyUseCase({
myService: container.get<MyService>('MY_SERVICE')
});
});
// or using symbols
container.bind(DI.MY_USE_CASE).toFactory(() => {
// Do something before creating the instance
// Then return the instance
return MyUseCase({
myService: container.get<MyService>(DI.MY_SERVICE)
});
});
类
您可以注册类; 类的依赖性将被解决并注入构建器
class MyServiceClass implements MyServiceClassInterface {
constructor(
private readonly dep1: string,
private readonly dep2: number,
) {}
runTask(): string {
return `Executing with dep1: ${this.dep1} and dep2: ${this.dep2}`;
}
}
container.bind('CLASS_WITH_DEPENDENCIES').toClass(MyServiceClass, ['DEP1', 'DEP2']);
// or using symbols
container.bind(DI.CLASS_WITH_DEPENDENCIES).toClass(MyServiceClass, [DI.DEP1, DI.DEP2]);
但如果你喜欢,也可以使用依赖对象:
interface Dependencies {
dep1: string,
dep2: number
}
class MyServiceClass implements MyServiceClassInterface {
constructor(private readonly dependencies: Dependencies) {}
runTask(): string {
return `Executing with dep1: ${this.dependencies.dep1} and dep2: ${this.dependencies.dep2}`;
}
}
container.bind('CLASS_WITH_DEPENDENCIES').toClass(MyServiceClass, {dep1: 'DEP1', dep2: 'DEP2'});
// or using symbols
container.bind(DI.CLASS_WITH_DEPENDENCIES).toClass(MyServiceClass, {dep1: DI.DEP1, dep2: DI.DEP2});
- 无需依赖即可注册课程:
class MyServiceClassWithoutDependencies implements MyServiceClassInterface {
runTask(): string {
return `Executing without dependencies`;
}
}
container.bind('CLASS_WITHOUT_DEPENDENCIES').toClass(MyServiceClassWithoutDependencies);
// or using symbols
container.bind(DI.CLASS_WITHOUT_DEPENDENCIES).toClass(MyServiceClassWithoutDependencies);
使用登记册解决依赖关系(建议)
您现在可以用容器的获取方法解决依赖性 .
import { DI } from './di';
// Primitive
// using strings
const dep1 = container.get('DEP1'); // type will be the one you registered in your registry (string here)
// or using symbols
const dep1 = container.get(DI.DEP1); // type will be the one you registered in your registry (string here)
在没有登记册的情况下解决依赖性
import { DI } from './di';
// Primitive
// using strings
const dep1 = container.get<string>('DEP1'); // By default, the type will be 'unknown', so you need to cast it manually
// or using symbols
const dep1 = container.get<string>(DI.DEP1); // By default, the type will be 'unknown', so you need to cast it manually
模块
您也可以使用模块来组织您的依赖。 模块支持与集装箱相同的类型安全登记办法.
正在装入模块
然后可将模块装入容器. 默认情况下,在创建容器时,会使用罩下的一个默认模块.
有书记官处(建议):
interface AppRegistry {
'DEP1': string;
'DEP2': number;
'MY_SERVICE': MyServiceInterface;
}
const module1 = createModule<AppRegistry>();
module1.bind('DEP1').toValue('dependency1');
const module2 = createModule<AppRegistry>();
module2.bind('DEP2').toValue(42);
const module3 = createModule<AppRegistry>();
module3.bind('MY_SERVICE').toHigherOrderFunction(MyService, {dep1: 'DEP1', dep2: 'DEP2'});
const container = createContainer<AppRegistry>();
container.load('module1', module1);
container.load('module2', module2);
container.load('module3', module3);
const myService = container.get('MY_SERVICE'); // Type: MyServiceInterface
未登记旧做法:
const module1 = createModule();
module1.bind(DI.DEP1).toValue('dependency1');
const module2 = createModule();
module2.bind(DI.DEP2).toValue(42);
const module3 = createModule();
module3.bind(DI.MY_SERVICE).toHigherOrderFunction(MyService, {dep1: DI.DEP1, dep2: DI.DEP2});
const container = createContainer();
container.load(Symbol('module1'), module1);
container.load(Symbol('module2'), module2);
container.load(Symbol('module3'), module3);
const myService = container.get<MyServiceInterface>(DI.MY_SERVICE);
依赖性不需要在使用它们的模块中注册. 注意作为密钥使用的模块名可以是符号或字符串.
模块覆盖
您也可以覆盖模块的依赖性 。 模块的依赖性将被上一个已上载模块的依赖性所压倒.
const module1 = createModule<AppRegistry>();
module1.bind(DI.DEP1).toValue('OLD dependency1');
module1.bind(DI.MY_SERVICE).toFunction(sayHelloWorld);
const module2 = createModule<AppRegistry>();
module2.bind(DI.DEP1).toValue('NEW dependency1');
const module3 = createModule<AppRegistry>();
module3.bind(DI.MY_SERVICE).toHigherOrderFunction(MyService, {dep1: DI.DEP1, dep2: DI.DEP2});
const container = createContainer<AppRegistry>();
container.bind(DI.DEP2).toValue(42); // Default module
container.load(Symbol('module1'), module1);
container.load(Symbol('module2'), module2);
container.load(Symbol('module3'), module3);
// The dependency DI.MY_SERVICE will be resolved with the higher-order function and dep1 will be 'NEW dependency1'
const myService = container.get(DI.MY_SERVICE);
卸载模块
也可以从容器中卸下一个模块. 模块的依赖性将从容器中去除. 已缓存实例将被删除,以保持一致性并避免可能出错.
const module1 = createModule<AppRegistry>();
module1.bind(DI.DEP1).toValue('dependency1');
const container = createContainer<AppRegistry>();
container.load(Symbol('module1'), module1);
container.unload(Symbol('module1'));
// Will throw an error as the dependency is not registered anymore
const myService = container.get(DI.DEP1);
使用范围
单通范围( 默认)
在单顿范围内,容器每次解决依赖性时都会返回同样的情况.
container.bind(DI.MY_SERVICE).toClass(MyServiceClass, [DI.DEP1, DI.DEP2]);
// or
container.bind(DI.MY_SERVICE).toClass(MyServiceClass, [DI.DEP1, DI.DEP2], 'singleton');
const instance1 = container.get(DI.MY_SERVICE);
const instance2 = container.get(DI.MY_SERVICE);
console.log(instance1 === instance2); // true
临时范围
在瞬态范围,容器每次解决依赖性时都会返回一个新的实例.
container.bind(DI.MY_SERVICE).toClass(MyServiceClass, [DI.DEP1, DI.DEP2], 'transient');
const instance1 = container.get(DI.MY_SERVICE);
const instance2 = container.get(DI.MY_SERVICE);
console.log(instance1 === instance2); // false
范围
在范围范围内,容器在一个范围内返回同一情况。 不同的范围会有不同的情况.
要使用范围范围,您需要使用 runInScope 创建范围.
container.bind(DI.MY_SERVICE).toClass(MyServiceClass, [DI.DEP1, DI.DEP2], 'scoped');
const instance1 = undefined;
const instance2 = undefined;
container.runInScope(() => {
instance1 = container.get(DI.MY_SERVICE);
instance2 = container.get(DI.MY_SERVICE);
console.log(instance1 === instance2); // true
});
container.runInScope(() => {
const instance3 = container.get(DI.MY_SERVICE);
console.log(instance3 === instance1); // false
});
注:如果试图解决范围外的依赖性,则会出现出错.
循环依赖关系
章鱼容器可以探测到循环依赖性. 如果检测到循环依赖, 则会出现出错 .
const container = createContainer<AppRegistry>();
const A_TOKEN = Symbol('A');
const B_TOKEN = Symbol('B');
class A {
constructor(public b: B) {}
}
class B {
constructor(public a: A) {}
}
container.bind(A_TOKEN).toClass(A, [B_TOKEN]);
container.bind(B_TOKEN).toClass(B, [A_TOKEN]);
container.get(A_TOKEN); // Will throw: "Circular dependency detected: Symbol(A) -> Symbol(B) -> Symbol(A)"
这样可以避免无限循环和堆叠溢出的错误.