打字稿-对装饰器的循环依赖

时间:2020-09-18 09:32:34

标签: javascript node.js typescript import circular-dependency

我对装饰器有循环依赖关系,因为我的类ThingAThingB有关系,反之亦然。 我已经阅读了有关此问题的几个问题:

但是我无法为我的案件找到有效的解决方案。 我尝试了很多人建议从@hasOne(ThingA)更改为@hasOne(() => ThingA)来强制延迟加载并破坏依赖关系,但是该解决方案不起作用,因为我无法获得构造函数名称。 我需要构造函数的名称(例如:“ ThingA”)将其添加到构造函数ThingB的元数据中。

以下是我的原始代码(未修改lazyload)

ThingA

@hasAtLeast(0, ThingB)
export class ThingA extends RTContent {
    @IsEmail()
    email: string;
}

事物B

@hasOne(ThingA)
export class ThingB extends RTContent {
    @IsString()
    description: string;
}

装饰器:

export type LinkConstraint<C extends RTContent> = {
    content: string; // name of the constructor (ex. 'ThingA')
    maxOccurrences: number;
    minOccurrences: number;
    constructor: { new(...args: any[]): C };
}

function constraintFactory<C extends RTContent>(minOccurrences: number, maxOccurrences: number, associated: { new(...args: any[]): C }) {
    return (constructor: Function) => {
        const constraints = Reflect.getMetadata('linkConstraints', constructor) || [];
        const constraint: LinkConstraint<C> = {
            content: associated?.name,
            minOccurrences,
            maxOccurrences,
            constructor: associated
        };
        constraints.push(constraint);
        Reflect.defineMetadata('linkConstraints', constraints, constructor)
    }
}

export function hasOne<C extends RTContent>(associated: { new(...args: any[]): C }) {
    return constraintFactory(1, 1, associated)
}

export function hasAtLeast<C extends RTContent>(minOccurrences: number, associated: { new(...args: any[]): C }) {
    return constraintFactory(minOccurrences, Infinity, associated)
}

1 个答案:

答案 0 :(得分:2)

我看到您的装饰器实际上并没有修改构造函数,它只是运行一些副作用代码来添加一些元数据条目。因此,@decorator语法不是必须的。

我的建议是,您既不装饰ThingA也不装饰ThingB,只需按原样导出即可。您将装饰推迟到另一个模块中,该模块应该是ThingAThingB的共同父对象。这样可以解决循环依赖性。

例如,您在'./things/index.ts'中执行

import { ThingA } from './things/ThingA';
import { ThingB } from './things/ThingB';

hasOne(ThingA)(ThingB);
hasAtLeast(0, ThingB)(ThingA);

export { ThingA, ThingB }

现在,您代码的其他部分可以从'./things/index.ts'导入,而不是直接从'./things/ThingA(or B).ts'导入。这样可以确保在实例化类之前执行装饰。


如果必须使用装饰器,那么最好是延迟加载。 @hasOne(() => ThingA)应该可以解决问题,但是您需要相应地修改hasOne的实现,有点麻烦。

function hasOne(target) {
  if (typeof target === "function") {
    setTimeout(() => {
      _workOnTarget(target())
    }, 0)
  } else {
    _workOnTarget(target)
  }
}

关键是要延迟访问变量值。

要使此技巧正常工作,我们仍然依靠以下事实:这些装饰器仅具有副作用,请不要修改构造器。因此,这不是循环依赖问题的一般解决方案。更一般的模式是偏离路线的懒惰评估。不过,如果您确实需要更复杂,请在评论中提问。

对于您的情况,上面的impl应该有效。但是您不能在任何模块的顶层内部实例化ThingA或B,因为在setTimeout回调之前可能会发生cuz,从而破坏了破解。