TypeScript 装饰器和循环依赖
TypeScript Decorators and Circular Dependencies
考虑使用装饰器的相互依赖代码示例(下方)。
现在考虑以下工作流程(是的,我确实想传递实际导出的 classes,因为我以后需要使用它们):
- 应用导入并运行
Parent.ts
@Test(Child)
导致应用在装饰时导入 Child.ts
- 注:class
Parent
尚未达到
- 在
Child.ts
中,@Test(Parent)
装饰器被执行
- 此时
Parent
未定义,无法传递给装饰器。
如您所见,存在令人讨厌的循环依赖,我看不出有什么方法可以应用以 classes 作为参数并相互引用的装饰器。
请注意,为了简洁起见,我使用 @Test
作为示例。实际的装饰器是 @HasMany
和 @BelongsTo
- 所以我在这里有一个实际的用例。
我的问题是: "Is there a solution to this issue?"
我担心没有,除非更改 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 {
}
如果您可以推迟在装饰器中执行的操作 - 您可以克服限制并打破循环依赖。从你真正的装饰者@HasMany 和@BelongsTo 的名字来看,你似乎在为每个 class 附加某种元数据以供以后使用——如果是这样,我的建议是:
- 扩展你的@Test装饰器签名
export function Test(passedClass: Function | string)
我在这里假设装饰器会将元信息存储在某种静态字典中,例如:。属性看起来像
{
hasMany: {new(): any} | string
belongsTo: {new(): any} | string
}
内部装饰器创建新属性对象,hasMany/belongsTo 属性设置为 passedClass
。如果 passedClass
不是字符串 - 检查所有已添加的属性并替换任何 hasMany/belongsTo 是字符串类型且等于当前 passedClass.name
从父项中删除对子项的引用。
这个实现有些幼稚,您可以实现一些私有字段来隐藏中间字符串数据并避免暴露联合类型字段。
希望对您有所帮助。
做同样的事情,但以不同的方式构建代码怎么样?
如果 Child
和 Parent
都驻留在同一个文件中,那么这应该不是问题。
这听起来可能不是最佳选择,因为根据长度和逻辑将代码分成模块很舒服,但这可以通过某种方式解决。
你可以有一个主文件,它的基础是 类,甚至是抽象的:
// 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 {}
今天遇到同样的问题。我通过将 @Test(Parent)
替换为 @Test(() => Parent)
.
来稍微不同地解决了它
我没有跟踪元数据中的 class 构造函数 (Parent
),而是跟踪 returns 构造函数 (() => Parent
) 的 thunk。这会延迟对 Parent
导入变量的评估,直到调用 thunk,这才有效。
考虑使用装饰器的相互依赖代码示例(下方)。
现在考虑以下工作流程(是的,我确实想传递实际导出的 classes,因为我以后需要使用它们):
- 应用导入并运行
Parent.ts
@Test(Child)
导致应用在装饰时导入Child.ts
- 注:class
Parent
尚未达到 - 在
Child.ts
中,@Test(Parent)
装饰器被执行 - 此时
Parent
未定义,无法传递给装饰器。
如您所见,存在令人讨厌的循环依赖,我看不出有什么方法可以应用以 classes 作为参数并相互引用的装饰器。
请注意,为了简洁起见,我使用 @Test
作为示例。实际的装饰器是 @HasMany
和 @BelongsTo
- 所以我在这里有一个实际的用例。
我的问题是: "Is there a solution to this issue?"
我担心没有,除非更改 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 {
}
如果您可以推迟在装饰器中执行的操作 - 您可以克服限制并打破循环依赖。从你真正的装饰者@HasMany 和@BelongsTo 的名字来看,你似乎在为每个 class 附加某种元数据以供以后使用——如果是这样,我的建议是:
- 扩展你的@Test装饰器签名
export function Test(passedClass: Function | string)
我在这里假设装饰器会将元信息存储在某种静态字典中,例如:。属性看起来像
{
hasMany: {new(): any} | string
belongsTo: {new(): any} | string
}
内部装饰器创建新属性对象,hasMany/belongsTo 属性设置为
passedClass
。如果passedClass
不是字符串 - 检查所有已添加的属性并替换任何 hasMany/belongsTo 是字符串类型且等于当前passedClass.name
从父项中删除对子项的引用。
这个实现有些幼稚,您可以实现一些私有字段来隐藏中间字符串数据并避免暴露联合类型字段。
希望对您有所帮助。
做同样的事情,但以不同的方式构建代码怎么样?
如果 Child
和 Parent
都驻留在同一个文件中,那么这应该不是问题。
这听起来可能不是最佳选择,因为根据长度和逻辑将代码分成模块很舒服,但这可以通过某种方式解决。
你可以有一个主文件,它的基础是 类,甚至是抽象的:
// 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 {}
今天遇到同样的问题。我通过将 @Test(Parent)
替换为 @Test(() => Parent)
.
我没有跟踪元数据中的 class 构造函数 (Parent
),而是跟踪 returns 构造函数 (() => Parent
) 的 thunk。这会延迟对 Parent
导入变量的评估,直到调用 thunk,这才有效。