简体   繁体   English

TypeScript装饰器和循环依赖项

[英]TypeScript Decorators and Circular Dependencies

Consider the sample of inter-dependent code (below) that makes use of decorators. 考虑一下使用修饰符的相互依赖的代码示例(如下)。

Now consider the following workflow (yes, I do want to pass the actual exported classes since I need to use them later): 现在考虑以下工作流程(是的,我确实想传递实际的导出类,因为以后需要使用它们):

  1. App imports and runs Parent.ts 应用程序导入并运行Parent.ts
  2. @Test(Child) causes the app to import Child.ts while decorating @Test(Child)使应用程序在装饰时导入Child.ts
  3. Note: the class Parent has not yet been reached by the code 注意:代码尚未到达Parent
  4. In Child.ts , the @Test(Parent) decorator is executed Child.ts ,执行@Test(Parent)装饰器
  5. At this point, Parent is undefined and cannot be passed to the decorator. 此时, Parent是未定义的,无法传递给装饰器。

As you see, there's a nasty circular dependency going on and I cannot see a way of being able to apply decorators that take classes as arguments which reference each other. 如您所见,有一个令人讨厌的循环依赖,并且我看不到能够应用将类作为彼此引用的参数的装饰器的方法。

Please note, I used @Test for brevity as an example. 请注意,为简便起见,我以@Test为例。 The actual decorators are @HasMany and @BelongsTo - so I do have an actual use case here. 实际的装饰器是@HasMany@BelongsTo所以我在这里有一个实际的用例。

My question to you: "Is there a solution to this issue?" 我对您的问题: “是否有解决此问题的方法?”

My fear is that there isn't, unless TypeScript's compiled code is changed so as to defer the decoration process until all involved code has been imported. 我担心的是,除非TypeScript的编译代码被更改以将修饰过程推迟到所有相关代码都被导入之前,否则没有。

Code example: 代码示例:

Decorators.ts : Decorators.ts

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

Parent.ts : Parent.ts

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

    @Test(Child)
    export class Parent {

    }

Child.ts : Child.ts

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

    @Test(Parent)
    export class Child {

    }

Hit the same problem today. 今天遇到同样的问题。 I solved it slightly differently, by replacing @Test(Parent) by @Test(() => Parent) . 我通过将@Test(() => Parent)替换为@Test(Parent)来解决问题。

Instead of tracking the class constructor ( Parent ) in metadata, I track the thunk that returns the constructor ( () => Parent ). 我没有跟踪元数据中的类构造函数( Parent ),而是跟踪返回构造函数( () => Parent )的thunk。 This delays the evaluation of the Parent imported variable until the thunk is invoked, which does the trick. 这将延迟对Parent导入变量的求值,直到调用thunk为止,从而达到目的。

If you can postpone actions performed in decorator - you can overcome the limitation and break circular dependency. 如果可以推迟在装饰器中执行的操作-您可以克服此限制并打破循环依赖关系。 From the names of your real decorators @HasMany and @BelongsTo it looks like you are attaching some sort of the metadata to each class for the later usage - if so here is my suggestion: 从真正的装饰器的名称@HasMany和@BelongsTo看来,您正在将某种元数据附加到每个类中供以后使用-如果是这样,这是我的建议:

  1. Extend your @Test decorator signature 扩展@Test装饰器签名

export function Test(passedClass: Function | string)

I assume here that the decorator will store meta information in some sort of static dictionary, like: . 我在这里假设装饰器会将元信息存储在某种静态字典中,例如:。 Where properties can look like 属性看起来像哪里

{
    hasMany: {new(): any} | string
    belongsTo: {new(): any} | string
}
  1. Inside decorator create new properties object with hasMany/belongsTo properties set to passedClass . 在装饰器内部,将hasMany / belongsTo属性设置为passedClass以创建新的属性对象。 If passedClass is not string - check all already added properties and replace any hasMany/belongsTo that are of string type and equal current passedClass.name 如果passedClass不是字符串,请检查所有已添加的属性,并替换所有字符串类型且当前等于passedClass.name hasMany / passedClass.name

  2. Remove reference to Child from Parent. 从父级删除对子级的引用。

This is somewhat naive implementation and you can implement some private fields instead to hide the intermediate string data and keep away from exposing union type fields. 这是有点天真的实现,您可以实现一些私有字段,而不是隐藏中间字符串数据,并避免暴露联合类型字段。

Hope this will help you. 希望这会帮助你。

How about doing the same, but structuring your code differently? 怎么做,但是结构不同呢?
If both Child and Parent reside in the same file then it shouldn't be a problem. 如果ChildParent都驻留在同一个文件中,那应该没有问题。

That might not sound optimal as it's comfortable to separate code to modules due to length and logic, but that can be solved in a way. 这听起来可能不是最佳的,因为由于长度和逻辑的原因,很容易将代码分离为模块,但这可以通过某种方式解决。
You can have a main file that has base classes for those, even abstract: 您可以拥有一个包含这些基本类(甚至是抽象类)的主文件:

// Base.ts
import {Test} from "./Decorators";

@Test(BaseChild)
export abstract class BaseParent {}

@Test(BaseParent)
export abstract class BaseChild {}

And then in your specific modules: 然后在您的特定模块中:

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

export class Parent extends BaseParent {}

And

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

export class Child extends BaseChild {}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM