TypeScript Decorators and Circular Dependencies

假如想象 提交于 2019-12-21 20:19:35

问题


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
  2. @Test(Child) causes the app to import Child.ts while decorating
  3. Note: the class Parent has not yet been reached by the code
  4. In Child.ts, the @Test(Parent) decorator is executed
  5. At this point, Parent is undefined and cannot be passed to the decorator.

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. The actual decorators are @HasMany and @BelongsTo - so I do have an actual use case here.

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.

Code example:

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 {

    }

回答1:


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:

  1. Extend your @Test decorator signature

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. 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

  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.




回答2:


Hit the same problem today. I solved it slightly differently, by replacing @Test(Parent) by @Test(() => Parent).

Instead of tracking the class constructor (Parent) in metadata, I track the thunk that returns the constructor (() => Parent). This delays the evaluation of the Parent imported variable until the thunk is invoked, which does the trick.




回答3:


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.

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


来源:https://stackoverflow.com/questions/39334698/typescript-decorators-and-circular-dependencies

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!