从 v6 迁移
InversifyJS v8 引入了一些重大更改。本指南将帮助你将现有的 InversifyJS v6 代码迁移到 v8。
更改概述
下表总结了从 v6 到 v8 的主要更改:
| v6 | v8 | 备注 |
|---|---|---|
autoBindInjectable 选项 | autobind 选项 | 重命名的容器构造函数选项 |
container.resolve(X) | container.get(X, { autobind: true }) | resolve 被带有 autobind 选项的 get 替换 |
| 自定义元数据和中间件 | 无直接替换 | 已删除以简化库并避免暴露内部数据结构 |
container.isBoundNamed | container.isBound(X, { name: ... }) | 合并了所有带有选项参数的 isBound 变体 |
container.isBoundTagged | container.isBound(X, { tag: ... }) | 合并了所有带有选项参数的 isBound 变体 |
container.getNamed | container.get(X, { name: ... }) | 合并了所有带有选项参数的 get 变体 |
container.getTagged | container.get(X, { tag: ... }) | 合并了所有带有选项参数的 get 变体 |
container.tryGet | container.get(X, { optional: true }) | 为可选的 get 添加了 optional 标志 |
container.tryGetNamed | container.get(X, { name: ..., optional: true }) | 组合了命名和可选参数 |
container.tryGetTagged | container.get(X, { tag: ..., optional: true }) | 组合了标记和可选参数 |
container.createChild() | new Container({ parent: container }) | 通过构造函数创建子容器 |
.toAutoFactory(X) | .toFactory((ctx) => () => ctx.get(X)) | 使用 toFactory 与 ResolutionContext.get() |
.toAutoNamedFactory(X) | .toFactory((ctx) => (name) => ctx.get(X, { name })) | 使用 toFactory 与命名解析 |
.toConstructor(X) | .toConstantValue(X) | 将构造函数绑定为常量值 |
.toFunction(fn) | .toConstantValue(fn) | 将函数绑定为常量值 |
interfaces.Context | ResolutionContext | 更新了绑定 API 中的上下文参数类型 |
interfaces.Request | BindingConstraints | 更新了绑定约束参数类型 |
interfaces.Provider<T> | 已删除 | 使用 Factory<T> 与 toFactory 和异步函数代替 |
interfaces.Factory<T> | Factory<T> | 直接导出,不再在 interfaces 命名空间下 |
interfaces.Newable<T> | Newable<T> | 直接导出,不再在 interfaces 命名空间下 |
| 隐式注入继承 | @injectFromBase 装饰器 | 使用装饰器的显式继承 |
import { interfaces } from "inversify"; | import { ... } from "inversify"; | 删除了 interfaces 命名空间,使用直接导入 |
| CJS/ESM 双包 | 仅 ESM | 包现在仅支持 ESM;Node.js LTS 支持 require(esm) |
.toProvider(...) | .toFactory(...) 与异步函数 | Provider 类型和 toProvider 绑定已删除;请改用 toFactory |
包格式
InversifyJS v8 现在是一个仅支持 ESM 的包。对于大多数用户来说,这不是一个破坏性更改:所有活跃的 Node.js LTS 版本(20.x 及以上)都支持 require(esm),这意味着 CommonJS 项目仍然可以在不做任何更改的情况下使用该包。
有关 CJS → ESM 迁移的更广泛概述,请参阅 将生态系统迁移到 ES 模块 博客文章。
容器 API
自动绑定
在 v6 中,通过将 autoBindInjectable 选项传递给容器构造函数来启用自动绑定。在 v8 中,此选项已重命名为 autobind,并且可以作为 Container 构造函数选项或 Container.get 选项的一部分传递。
在 v6 中,container.resolve 自动将解析的服务绑定到容器。在 v8 中,此行为已被删除。要启用它,请传递 autobind 选项。
export class Katana {
public readonly damage: number = 10;
}
@injectable()
export class Samurai {
public readonly katana: Katana;
constructor(katana: Katana) {
this.katana = katana;
}
}
const container: Container = new Container();
const samurai: Samurai = container.get(Samurai, { autobind: true });
自定义元数据和中间件
此功能已在 v8 中删除,没有直接替换。它没有被广泛使用,并且增加了库的复杂性。将来可能会引入更好的 API。
类似 isBound 的方法
像 Container.isBoundNamed 和 Container.isBoundTagged 这样的方法已被 Container.isBound 替换,带有一个可选的 isBoundOptions 参数来处理命名和标记绑定。
有关更多详细信息,请参阅 isBound 和 isCurrentBound 的 API 文档。
类似 get 的方法
Container.getNamed、Container.getTagged、Container.tryGet、Container.tryGetNamed 和 Container.tryGetTagged 方法已被带有 OptionalGetOptions 参数的 Container.get 替换。
同样,Container.getAll、Container.getAllAsync 和 Container.getAsync 现在接受 GetOptions 对象来指定名称或标签。
const container: Container = new Container();
container.bind<Weapon>('Weapon').to(Katana).whenNamed('Katana');
const katana: Weapon = container.get<Weapon>('Weapon', { name: 'Katana' });
此外,Container.getAll 和 Container.getAllAsync 现在强制执行绑定约束。例如:
const container: Container = new Container();
container.bind<Weapon>('Weapon').to(Katana);
container.bind<Weapon>('Weapon').to(Shuriken).whenNamed('ranged');
const weapons: Weapon[] = container.getAll<Weapon>('Weapon');
在 v6 中,container.getAll 返回与服务标识符匹配的所有绑定。在 v8 中,它仅返回与服务标识符和绑定约束都匹配的绑定。
如果你需要模拟旧行为,可以使用自定义约束来实现。有关更多信息,请参阅此 问题。
父容器和子容器
在 v6 中,使用 createChild 方法创建子容器。在 v8 中,此方法已被删除。相反,将父容器传递给子容器的构造函数。
class Katana {}
const parentContainer: Container = new Container();
parentContainer.bind(weaponIdentifier).to(Katana);
const childContainer: Container = new Container({ parent: parentContainer });
const katana: Katana = childContainer.get(weaponIdentifier);
容器模块 API
容器模块加载选项现在作为对象传递。
const warriorsModule: ContainerModule = new ContainerModule(
(options: ContainerModuleLoadOptions) => {
options.bind<Ninja>('Ninja').to(Ninja);
},
);
const weaponsModule: ContainerModule = new ContainerModule(
(options: ContainerModuleLoadOptions) => {
options.bind<Katana>('Weapon').to(Katana).whenNamed('Melee');
options.bind<Shuriken>('Weapon').to(Shuriken).whenNamed('Ranged');
},
);
container.load(warriorsModule, weaponsModule);
const ninja: Ninja = container.get('Ninja');
BindingFluentSyntax API
ResolutionContext 代替 interfaces.Context
Context 类已被 ResolutionContext 替换,以简化 API 并隐藏内部数据结构。
@injectable()
class Katana {
public use(): string {
return 'hit!';
}
}
container
.bind<Katana>('Katana')
.to(Katana)
.onActivation((_context: ResolutionContext, katana: Katana) => {
const handler: ProxyHandler<() => string> = {
apply: function (
target: () => string,
thisArgument: unknown,
argumentsList: [],
) {
console.log(`Starting: ${new Date().getTime().toString()}`);
const result: string = target.apply(thisArgument, argumentsList);
console.log(`Finished: ${new Date().getTime().toString()}`);
return result;
},
};
katana.use = new Proxy(katana.use.bind(katana), handler);
return katana;
});
BindingConstraints 代替 interfaces.Request
Request 对象已被 BindingConstraints 替换,以简化 API 并隐藏内部数据结构。
const ninjaId: symbol = Symbol.for('Ninja');
const weaponId: symbol = Symbol.for('Weapon');
@injectable()
class Ninja {
constructor(
@inject(weaponId)
@named('shuriken')
public readonly weapon: Weapon,
) {}
}
container.bind<Ninja>(ninjaId).to(Ninja);
const whenTargetNamedConstraint: (
name: string,
) => (bindingconstraints: BindingConstraints) => boolean =
(name: string) =>
(bindingconstraints: BindingConstraints): boolean =>
bindingconstraints.name === name;
container
.bind<Weapon>(weaponId)
.to(Katana)
.when(whenTargetNamedConstraint('katana'));
container
.bind<Weapon>(weaponId)
.to(Shuriken)
.when(whenTargetNamedConstraint('shuriken'));
const ninja: Ninja = container.get(ninjaId);
// Returns 5
const ninjaDamage: number = ninja.weapon.damage;
已删除的绑定方法
在 v8 中,多个绑定方法已被删除以简化 API。以下是每个方法的迁移方式:
toAutoFactory
toAutoFactory 方法已被删除。请改用 toFactory 与 ResolutionContext.get():
@injectable()
export class Ninja {
readonly #katana: Katana;
constructor(@inject('Factory<Katana>') katanaFactory: () => Katana) {
this.#katana = katanaFactory();
}
public fight(): string {
return this.#katana.hit();
}
}
export const container: Container = new Container();
container.bind('Katana').to(Katana);
// v6: container.bind('Factory<Katana>').toAutoFactory<Katana>('Katana');
container
.bind<() => Katana>('Factory<Katana>')
.toFactory(
(context: ResolutionContext) => () => context.get<Katana>('Katana'),
);
container.bind(Ninja).toSelf();
toAutoNamedFactory
同样,toAutoNamedFactory 已被删除。请使用 toFactory 与命名解析:
@injectable()
export class Ninja {
readonly #katana: Katana;
readonly #shuriken: Shuriken;
constructor(
@inject('Factory<Weapon>')
weaponFactory: (name: MetadataName) => Weapon,
) {
this.#katana = weaponFactory('katana') as Katana;
this.#shuriken = weaponFactory('shuriken') as Shuriken;
}
public fight(): string {
return this.#katana.hit();
}
public sneak(): string {
return this.#shuriken.throw();
}
}
export const container: Container = new Container();
container.bind<Weapon>('Weapon').to(Katana).whenNamed('katana');
container.bind<Weapon>('Weapon').to(Shuriken).whenNamed('shuriken');
// v6: container.bind('Factory<Weapon>').toAutoNamedFactory<Weapon>('Weapon');
container
.bind<(name: MetadataName) => Weapon>('Factory<Weapon>')
.toFactory(
(context: ResolutionContext) => (name: MetadataName) =>
context.get<Weapon>('Weapon', { name }),
);
container.bind(Ninja).toSelf();
toConstructor
toConstructor 方法已被删除。由于构造函数只是一个值(一个可调用的值),请改用 toConstantValue:
const container: Container = new Container();
// v6: container.bind('WeaponConstructor').toConstructor(Katana);
container.bind<Newable<Weapon>>('WeaponConstructor').toConstantValue(Katana);
export const weaponClass: Newable<Weapon> =
container.get<Newable<Weapon>>('WeaponConstructor');
export const weapon: Weapon = new weaponClass();
toFunction
toFunction 方法已被删除。由于函数只是一个值,请改用 toConstantValue:
function greet(name: string): string {
return `Hello, ${name}!`;
}
const container: Container = new Container();
// v6: container.bind('greet').toFunction(greet);
container.bind<typeof greet>('greet').toConstantValue(greet);
export const greetFn: (name: string) => string = container.get('greet');
export const greeting: string = greetFn('World');
toProvider
toProvider 绑定方法和 Provider 类型已被删除。提供者是返回 Promise<T> 的工厂,因此可以使用 toFactory 与异步函数实现相同的行为:
export const container: Container = new Container();
container.bind<Sword>('Sword').to(Katana);
// v6: container.bind<SwordProvider>('SwordProvider').toProvider<Sword>((context: interfaces.Context) => { ... });
container
.bind<SwordProvider>('SwordProvider')
.toFactory((context: ResolutionContext) => {
return async (material: string, damage: number): Promise<Sword> => {
return new Promise<Sword>(
(resolve: (value: Sword | PromiseLike<Sword>) => void) => {
setTimeout(() => {
const sword: Sword = context.get<Sword>('Sword');
sword.material = material;
sword.damage = damage;
resolve(sword);
}, 10);
},
);
};
});
装饰器 API
继承
注入继承现在使用 @injectFromBase 装饰器显式进行。这提供了更多控制,并避免了与构造函数参数不匹配相关的边缘情况。
有关更多详细信息,请参阅 继承文档。