Typescript-デコレータへの循環依存

Sep 18 2020

私のクラスThingAはと関係がThingBあり、その逆もあるため、デコレータに循環依存関係になります。この問題に関するいくつかの質問を読みました。

  • Javascript / Typescriptの循環依存問題の美しい修正

  • TypeScriptデコレータと循環依存

しかし、私は自分のケースに有効な解決策を見つけることができませんでした。多くの人々がから変更に提案として私が試し@hasOne(ThingA)に@hasOne(() => ThingA)遅延読み込みを強制し、依存関係を壊すために、私は、コンストラクタの名前を取得することはできませんだから、このソリューションは動作しません。コンストラクターThingBのメタデータに追加するには、コンストラクターの名前(例: 'ThingA')が必要です。

私の元のコードに従う(lazyloadの変更なし)

ThingA

@hasAtLeast(0, ThingB)
export class ThingA extends RTContent {
    @IsEmail()
    email: string;
}

ThingB

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

回答

2 hackape Sep 26 2020 at 14:26

デコレータは実際にはコンストラクタを変更せず、メタデータエントリを追加するためにいくつかの副作用コードを実行するだけです。したがって、@decorator構文は必須ではありません。

私のアドバイスは、装飾も装飾もせThingAずThingB、そのままエクスポートすることです。別のモジュールで装飾を延期します。これは、との両方の共通の親である必要がThingAありThingBます。このようにして、循環依存が解決されます。

たとえば、次のようにし'./things/index.ts'ます。

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

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

export { ThingA, ThingB }

これで、コードの他の部分をから'./things/index.ts'直接インポートするのではなく、からインポートできます'./things/ThingA(or B).ts'。これにより、クラスのインスタンス化の前に装飾が確実に実行されます。


デコレータを使用する必要がある場合は、遅延読み込みが最善の策です。@hasOne(() => ThingA)トリックを行う必要がありますが、hasOneそれに応じて、少しハックの実装を変更する必要があります。

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

重要なのは、変数値へのアクセスを遅らせることです。

このハックが機能するためには、これらのデコレータは副作用のみであり、コンストラクタを変更しないという事実に依然依存しています。したがって、これは循環依存問題の一般的な解決策ではありません。より一般的なパターンは、コース外の遅延評価です。しかし、もっと複雑ですが、本当に必要な場合は、plsはコメントで尋ねます。

あなたの場合、上記のimplは機能するはずです。ただしThingA、モジュールのトップレベル内でインスタンス化またはBを実行してはなりませんsetTimeout。これは、コールバックの前に発生するため、ハックを中断します。