【问题标题】:TypeScript Decorators and Circular DependenciesTypeScript 装饰器和循环依赖
【发布时间】:2016-09-05 16:40:19
【问题描述】:

考虑使用装饰器的相互依赖代码示例(如下)。

现在考虑以下工作流程(是的,我确实想传递实际导出的类,因为我以后需要使用它们):

  1. 应用导入并运行Parent.ts
  2. @Test(Child) 导致应用在装饰时导入 Child.ts
  3. 注意:代码尚未到达类Parent
  4. Child.ts 中,@Test(Parent) 装饰器被执行
  5. 此时,Parent 未定义,无法传递给装饰器。

如您所见,存在令人讨厌的循环依赖,我看不到一种能够应用将类作为相互引用的参数的装饰器的方法。

请注意,为了简洁起见,我使用了@Test 作为示例。实际的装饰器是 @HasMany@BelongsTo - 所以我这里有一个实际的用例。

我的问题是:“这个问题有解决方案吗?”

我担心没有,除非 TypeScript 的编译代码被更改,以便将装饰过程推迟到所有涉及的代码都被导入。

代码示例:

Decorators.ts:

    export function Test(passedClass: Function): Function {
        return function (model: Function): void {
            console.log(typeof passedClass);
        };
    }

Parent.ts:

    import {Child} from "./Child";
    import {Test} from "./Decorators";

    @Test(Child)
    export class Parent {

    }

Child.ts:

    import {Parent} from "./Parent";
    import {Test} from "./Decorators";

    @Test(Parent)
    export class Child {

    }

【问题讨论】:

  • 这不是递归,因为它是循环依赖。为什么每个班级都不能自己测试?
  • 谢谢 - 循环引用 - 这是我所掌握的术语。为简洁起见,我将其命名为 @Test。实际上是@HasMany@BelongsTo

标签: javascript typescript undefined decorator circular-dependency


【解决方案1】:

今天遇到同样的问题。我通过将@Test(Parent) 替换为@Test(() => Parent) 解决了这个问题。

我不是在元数据中跟踪类构造函数 (Parent),而是跟踪返回构造函数 (() => Parent) 的 thunk。这会延迟对 Parent 导入变量的评估,直到调用 thunk 为止。

【讨论】:

    【解决方案2】:

    如果您可以推迟在装饰器中执行的操作 - 您可以克服限制并打破循环依赖。从您真正的装饰器@HasMany 和@BelongsTo 的名称来看,您似乎正在将某种元数据附加到每个类以供以后使用 - 如果是这样,这是我的建议:

    1. 扩展您的@Test 装饰器签名

    export function Test(passedClass: Function | string)

    我在这里假设装饰器会将元信息存储在某种静态字典中,例如:.属性看起来像的地方

    {
        hasMany: {new(): any} | string
        belongsTo: {new(): any} | string
    }
    
    1. 在装饰器内部创建新的属性对象,其中 hasMany/belongsTo 属性设置为 passedClass。如果 passedClass 不是字符串 - 检查所有已添加的属性并替换任何字符串类型且等于当前 passedClass.name 的 hasMany/belongsTo

    2. 从父级中删除对子级的引用。

    这有点幼稚的实现,您可以实现一些私有字段来隐藏中间字符串数据并避免暴露联合类型字段。

    希望这会对你有所帮助。

    【讨论】:

    • 不幸的是,这不起作用,因为传递类的行为会导致循环依赖(装饰器执行的所有操作都已推迟到我的引导过程结束)。不过谢谢!
    • tjis 方法背后的想法是传递字符串而不是类来跳过引入循环依赖
    • 哦,对了,我很抱歉,Amid。我想我只是在努力寻找一种方法来获取实际的,例如 "User" 中的 User 类。我再试一次。
    • 通常这不是问题,因为您的所有“实体”都不可避免地会被添加相应元数据的装饰器标记。如果您引入让我们说“实体”装饰器 - 它将把字符串类名的静态映射填充到它们的构造函数中。之后,您将可以随时通过仅使用其字符串名称来构造实体(在您的情况下为用户)。
    • 好的,我很抱歉。我完全误解了这种方法。我现在已经实现了它,只需按类名搜索我的元数据存储,然后设置实体关联,它就可以完美地工作。
    【解决方案3】:

    如何做同样的事情,但以不同的方式构建代码?
    如果ChildParent 都驻留在同一个文件中,那么这应该不是问题。

    这听起来可能不是最佳选择,因为由于长度和逻辑,将代码分成模块很舒服,但这可以通过某种方式解决。
    您可以拥有一个主文件,其中包含这些基类,甚至是抽象类:

    // Base.ts
    import {Test} from "./Decorators";
    
    @Test(BaseChild)
    export abstract class BaseParent {}
    
    @Test(BaseParent)
    export abstract class BaseChild {}
    

    然后在您的特定模块中:

    // Parent.ts
    import {BaseParent} from "./Base";
    
    export class Parent extends BaseParent {}
    

    // Child.ts
    import {BaseChild} from "./Base";
    
    export class Child extends BaseChild {}
    

    【讨论】:

    • 我喜欢将装饰器移动到父类可以解决循环依赖问题(+1)的方式。最后,我确实选择了发布的另一个解决方案,因为它允许我将所有元数据装饰保留在主实体/模型文件中。谢谢!
    猜你喜欢
    • 2021-01-05
    • 2016-02-27
    • 2019-12-09
    • 2013-11-22
    • 2017-02-05
    • 2017-07-05
    • 2015-01-25
    • 1970-01-01
    相关资源
    最近更新 更多