打字稿-对装饰器的循环依赖 [英] Typescript - Circular dependency on decorator

查看:53
本文介绍了打字稿-对装饰器的循环依赖的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I get a circular dependency on my decorators because my class ThingA has a relation with ThingB and vice versa. I've read several questions about this issue:

TypeScript装饰器和循环依赖项

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

But I wasn't able to find a valid solution for my case. I tried as many people suggest to change from @hasOne(ThingA) to @hasOne(() => ThingA) to force a lazy loading and break the dependency, but this solution doesn't work because I'm not able to get the constructor name. I need the name of the constructor (example: 'ThingA') to add it in metadata of constructor ThingB.

跟随我的原始代码(无需进行lazyload修改)

Following my original code (without lazyload modification)

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


推荐答案

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

I see that your decorator doesn't actually modify the constructor, it merely runs some side-effect code to add some metadata entry. Thus the @decorator syntax isn't a must.

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

My advice is that you decorate neither ThingA nor ThingB, just export them as-is. You defer the decoration in another module, which should be the common parent of both ThingA and ThingB. This way circular dependency is resolved.

例如,在'./ things / index.ts'中,您执行以下操作:

For example, in './things/index.ts' you do:

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

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

export { ThingA, ThingB }

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

Now other part of your code can import from './things/index.ts', instead of directly from './things/ThingA(or B).ts'. This would ensure the decoration is executed before instantiation of classes.

如果必须使用装饰器,那么最好的选择是惰性加载。 @hasOne(()=> ThingA)应该可以解决问题,但是您需要修改 hasOne 的实现

If you must use decorator, well lazy load is your best bet. @hasOne(() => ThingA) should does the trick, but you need to modify the implementation of hasOne accordingly, a little hack.

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

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

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

For this hack to work, we still rely on the fact that these decorators are side-effect only, don’t modify the constructor. So this is NOT a general solution to circular dependency problem. More general pattern is off course lazy evaluation. More complicated though, if you really need, pls ask in comments.

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

For your case, above impl should work. But you must not instantiate ThingA or B right inside any module’s top level, cuz that would happen before setTimeout callback, thus break the hack.

这篇关于打字稿-对装饰器的循环依赖的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆