【问题标题】:Typescript - Circular dependency on decoratorTypescript - 对装饰器的循环依赖
【发布时间】:2021-01-05 05:40:23
【问题描述】:

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

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

按照我的原始代码(没有延迟加载修改)

事物A

@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)
}

【问题讨论】:

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


    【解决方案1】:

    我看到你的装饰器实际上并没有修改构造函数,它只是运行一些副作用代码来添加一些元数据条目。因此@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(() =&gt; ThingA) 应该可以解决问题,但是您需要相应地修改 hasOne 的实现,有点 hack。

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

    关键是延迟访问变量值。

    为了让这个 hack 起作用,我们仍然依赖于这些装饰器只是副作用的事实,不要修改构造函数。所以这不是循环依赖问题的一般解决方案。更一般的模式是惰性评估。不过比较复杂,如果你真的需要,请在 cmets 中询问。

    对于您的情况,上面的 impl 应该可以工作。但是你不能在任何模块的顶层内实例化 ThingA 或 B,因为这会在 setTimeout 回调之前发生,从而打破黑客攻击。

    【讨论】:

    • 感谢您的回复,我会尝试您的解决方案,但我会通过装饰器获得解决方案,我知道这不是强制性的,但这种方式在许多 ORM 中很常用,我想有一些模式可以打破这一点循环依赖
    猜你喜欢
    • 1970-01-01
    • 2019-12-09
    • 2016-02-27
    • 2017-07-05
    • 2017-02-05
    • 2013-11-22
    • 1970-01-01
    • 1970-01-01
    • 2019-11-24
    相关资源
    最近更新 更多