Могут ли внешние модули typescript иметь круговые зависимости?

Похоже, это не разрешено. requireJS выдает ошибку при следующем (этот пост отличается, поскольку он был разрешен с помощью внутренних модулей):

element.ts:

import runProperties = require('./run-properties');
export class Element {
   public static factory (element : IElement) : Element {

        switch (element.type) {
            case TYPE.RUN_PROPERTIES :
                return new runProperties.RunProperties().deserialize(<runProperties.IRunProperties>element);
        }
        return null;
    }
}

Run-properties.ts:

import element = require('./element');

export class RunProperties extends element.Element implements IRunProperties {
}

Ответ 1

Нет, модули не могут иметь круговые зависимости, если они не находятся в одном файле. Каждый файл обрабатывается последовательно, синхронно, поэтому полное определение файла (включая все экспортные данные, например) не было завершено, когда оно переходит во второй файл, который сразу же пытается потребовать/ссылаться на первый файл и т.д.,

Обычно вы можете разбить циклическую зависимость, введя интерфейс или базовый класс в общий файл определения (в основном, только интерфейсы), а другие файлы используют это как общий "интерфейс", а не напрямую ссылаются на классы, Это типичный шаблон на многих платформах.

Ответ 2

У меня такая же проблема, я смог ее исправить, создав класс factory, который позволяет регистрировать дочерние классы и использовать Generics для создания экземпляра.

Ссылка: https://www.typescriptlang.org/docs/handbook/generics.html#using-class-types-in-generics

См. пример кода ниже:

Базовый класс (abstract.control.ts)

export type AbstracControlOptions = {
    key?:string;
}
export abstract class AbstractControl {
    key:string;
    constructor(options:AbstracControlOptions){
        this.key = options.key;
    }    
}

Родительский класс (container.ts)

import { AbstractControl, AbstracControlOptions } from './abstract.control';
import { Factory } from './factory';

export { AbstracControlOptions };
export abstract class Container extends AbstractControl {
    children: AbstractControl[] = [];
    constructor(options: AbstracControlOptions) {
        super(options);
    }
    addChild(options: { type: string }) {
        var Control:any = Factory.ControlMap[options.type];
        if (Control) {
            this.children.push(Factory.create(Control, options));
        }
    }
}

Мне больше не нужно импортировать дочерние классы, потому что я использую factory.ts для создания экземпляров дочерних классов.

Factory Класс (factory.ts)

import {AbstractControl, AbstracControlOptions} from './abstract.control';

type ControlMap<T extends AbstractControl> = {
    [type:string]:T
};

export class Factory{
    static ControlMap: ControlMap<any> = {};
    static create<T extends AbstractControl>(c: { new ({}): T; }, options: AbstracControlOptions): T {
        return new c(options);
    } 
}

Хотя конструктор классов, по-видимому, вызывается в c: { new ({}): T }, но на самом деле его не вызывает. Но получает ссылку на конструктор с помощью оператора new. Параметр {} для конструктора в моем случае требуется, потому что это требует базовый класс AbstractControl.

(1) Детский класс (layout.ts)

import { Factory } from './factory';
import { Container, AbstracControlOptions } from './container';

export type LayoutlOptions = AbstracControlOptions & {
    type:"layout";
}
export class Layout extends Container {
    type: string = "layout";
    constructor(options:LayoutlOptions) {
        super(options);
    }
}
Factory.ControlMap["layout"] = Layout;

(2) Детский класс (повторитель .ts)

import { Factory } from './factory'
import { Container, AbstracControlOptions } from './container';

export type RepeaterOptions = AbstracControlOptions & {
    type: "repeater";
}
export class Repeater extends Container {
    type: string = "repeater";
    constructor(options:RepeaterOptions) {
        super(options);
    }
}
Factory.ControlMap["repeater"] = Repeater;