跳到主要内容
版本:Next

从 v6 迁移

InversifyJS v8 引入了一些重大更改。本指南将帮助你将现有的 InversifyJS v6 代码迁移到 v8。

更改概述

下表总结了从 v6 到 v8 的主要更改:

v6v8备注
autoBindInjectable 选项autobind 选项重命名的容器构造函数选项
container.resolve(X)container.get(X, { autobind: true })resolve 被带有 autobind 选项的 get 替换
自定义元数据和中间件无直接替换已删除以简化库并避免暴露内部数据结构
container.isBoundNamedcontainer.isBound(X, { name: ... })合并了所有带有选项参数的 isBound 变体
container.isBoundTaggedcontainer.isBound(X, { tag: ... })合并了所有带有选项参数的 isBound 变体
container.getNamedcontainer.get(X, { name: ... })合并了所有带有选项参数的 get 变体
container.getTaggedcontainer.get(X, { tag: ... })合并了所有带有选项参数的 get 变体
container.tryGetcontainer.get(X, { optional: true })为可选的 get 添加了 optional 标志
container.tryGetNamedcontainer.get(X, { name: ..., optional: true })组合了命名和可选参数
container.tryGetTaggedcontainer.get(X, { tag: ..., optional: true })组合了标记和可选参数
container.createChild()new Container({ parent: container })通过构造函数创建子容器
.toAutoFactory(X).toFactory((ctx) => () => ctx.get(X))使用 toFactoryResolutionContext.get()
.toAutoNamedFactory(X).toFactory((ctx) => (name) => ctx.get(X, { name }))使用 toFactory 与命名解析
.toConstructor(X).toConstantValue(X)将构造函数绑定为常量值
.toFunction(fn).toConstantValue(fn)将函数绑定为常量值
interfaces.ContextResolutionContext更新了绑定 API 中的上下文参数类型
interfaces.RequestBindingConstraints更新了绑定约束参数类型
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.isBoundNamedContainer.isBoundTagged 这样的方法已被 Container.isBound 替换,带有一个可选的 isBoundOptions 参数来处理命名和标记绑定。

有关更多详细信息,请参阅 isBoundisCurrentBound 的 API 文档。

类似 get 的方法

Container.getNamedContainer.getTaggedContainer.tryGetContainer.tryGetNamedContainer.tryGetTagged 方法已被带有 OptionalGetOptions 参数的 Container.get 替换。

同样,Container.getAllContainer.getAllAsyncContainer.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.getAllContainer.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 方法已被删除。请改用 toFactoryResolutionContext.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 装饰器显式进行。这提供了更多控制,并避免了与构造函数参数不匹配相关的边缘情况。

有关更多详细信息,请参阅 继承文档