【问题标题】:How to move typescript classes with circular dependency into separate files如何将具有循环依赖的打字稿类移动到单独的文件中
【发布时间】:2021-02-13 18:14:42
【问题描述】:

在一个 Angular 项目中,我们有一个基类,从该基类派生了几个具体的类。在少数情况下,这些类以循环方式相互引用。

当这些文件在不同的类中时,项目编译成功但在浏览器中运行失败。

当所有类都放在一个 .ts 文件中时,一切正常,但我们有一个非常长且难以维护的文件。

有没有办法分离这些文件?即使是合并文件(以智能方式)的现有预编译任务也会受到赞赏。

编辑: 我熟悉导入、模块等。问题是循环依赖。它也不能通过更多的抽象来优雅地处理。我尝试添加一个最小的复制代码。

编辑: 这是一个复制,创建一个警告:它还显示了我开始循环依赖的原因。

base-class.ts:

import {StorageModel} from './storage-model';
import {SubClass1} from './sub-class-1';

export abstract class BaseClass {
  children: BaseClass[];

  abstract loadFromModel(model: StorageModel);

  doSomething() {
    if (this.children[0] instanceof SubClass1) {
      (this.children[0] as SubClass1).action1();
    }
  }
}

sub-class-1.ts:

import {BaseClass} from './base-class';
import {StorageModel} from './storage-model';
import {SubClass2} from './sub-class-2';

export class SubClass1 extends BaseClass {
  title: string = 'hello';
  action1() {
  }

  loadFromModel(model: StorageModel) {
     (this.children[0] as SubClass2).action2();
  }
}

和类似的`sub-class-2.ts。 我在 angular cli 控制台中收到以下警告:

WARNING in Circular dependency detected:
src/models/sub-class-1.ts -> src/models/base-class.ts -> src/models/sub-class-1.ts

在浏览器控制台中:

[WDS] Warnings while compiling.

Circular dependency detected:
src/models/base-class.ts -> src/models/sub-class-1.ts -> src/models/base-class.ts

也就是说,代码正在运行。 我可以将 load fromModel 方法移动到访问者类(我实际上在项目中),但是我不会从重载和局部性中受益(加载逻辑在物理上接近所有相关代码)。

但是原始代码库会产生以下错误:

Uncaught TypeError: Object prototype may only be an Object or null: undefined
at setPrototypeOf (<anonymous>)
at __extends (navigation-list-filter-node.ts:7)

__extends 函数不是我的代码:

var __extends = (this && this.__extends) || (function () {
    var extendStatics = Object.setPrototypeOf ||
        ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
       function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
    return function (d, b) {
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = 
b.prototype, new __());
    };
})();

【问题讨论】:

  • 如果您需要多个模块中的组件,则需要一个额外的模块来声明和导出该组件,然后只需将该模块导入您需要该组件的任何位置。
  • 是的,您可以将类分成单独的 .ts 文件。您只需要使用 export 关键字来导出类。您可以使用 import {yourclassname} from 'path' 导入您的课程
  • 请发布重现您的特定问题的最小示例
  • 你需要考虑你的结构/逻辑。如果基类依赖于继承它的类,继承就没有任何意义。继承的重点是基础不需要知道任何关于它的扩展或实现
  • 在我看来,这些妥协从来没有必要,只需要更好的架构/数据建模。如果您期望进行大量维护,那么值得花时间正确地进行维护,如果您不这样做,从长远来看会伤害您。继承可能是一个有用的工具,但如果基础与实现紧密耦合,那么它是一个主要的危险信号,表明你做错了什么

标签: angular typescript


【解决方案1】:

问题

问题在于 Angular CLI 内部使用的 Webpack 对其架构有严格的导入规则。导入的顺序很重要,如果最终的 JavaScript 代码具有循环依赖(模块 1 导入模块 2,模块 2 导入模块 1),那么导入机制就会被破坏。

有趣的是,并非每次导入一些循环依赖都会成为问题。循环依赖是一个运行时 JavaScript 问题,而不是 TypeScript 的问题。因此,主要标准是:不要让导入的循环依赖项通过 JavaScript。这意味着:将它们用作类型,但不要与它们进行比较,不要调用它们,不要将它们作为函数参数传递,等等。

循环依赖的有效用法(不会导致错误):

const a: SubClass1 = this; // type casting -> TS only
const b = <SubClass1>this; // type casting -> TS only

为什么这不会导致 JavaScript 循环依赖?因为在 TypeScript 编译完成后,SubClass1 会消失:JavaScript 没有类型声明。

无效使用(导致循环依赖错误):

const c = instanceof SubClass1 // passing as argument -> JS
const d = new SubClass1() // calling -> JS
const e = SubClass1 // saving as value -> JS

现在回到原来的问题:

doSomething() {
  if (this.children[0] instanceof SubClass1) {
    (this.children[0] as SubClass1).action1();
  }
}

准确地说,它只是在这一行if (this.children[0] instanceof SubClass1) {,因为在 typescript 编译后,你转换类型的下面的行将被切断。基类中的instanceof 是循环依赖的典型原因,有多种解决方法。大多数情况下,您需要摆脱 instanceof 以支持其他内容。

解决方案 1. 在每个子类上添加限定符属性并强制实现:

export abstract class BaseClass {

  abstract get qualifier(): string;

  doSomething() {
    if (this.qualifier === 'SubClass1') { ... }
  }

}

export class SubClass1 extends BaseClass {

  get qualifier() {
    return 'SubClass1';
  }

}

丑陋,但功能齐全。在有很多类并且您需要随时区分所有类的情况下提供帮助。

解决方案 2。 添加例如布尔限定符,它清楚地描述了一个特定的意图(典型的例子是一些类需要在超类中实现的相同特性),并用一些默认值实例化它:

export abstract class BaseClass {

  get isDuck() {
    return false; // by default is not a duck
  }

  doSomething() {
    if (this.isDuck) { ... }
  }

}

// this one is a duck
export class SubClass1 extends BaseClass {

  get isDuck() {
    return true;
  }

}

// this one is not a duck. Why the hell should it be a duck? :D
export class SubClass2 extends BaseClass {}

第二种解决方案要好一些,因为限定不是由超类完成,而是由子类本身完成,因此子类以更细化的方式定义了谁/什么。在这种情况下,超类唯一要做的就是根据功能属性决定要做什么。

解决方案 3. 只需将逻辑移至子类即可;让他们每个人实现它的逻辑

export abstract class BaseClass {
  children: BaseClass[];

  abstract loadFromModel(model: StorageModel);

  abstract doSomething(): void;
}

export class SubClass1 extends BaseClass {
  action1() {
  }

  doSomething() {
    this.action1();
  }
}

这是最简单的,但并不总是足够的。

【讨论】:

  • 哇。这就是我所说的完美答案。它解释了导致问题的机制以及解决问题的方法。我会更深入地研究答案,稍后再回来。
  • 您的回答帮助我消除了运行时错误。我仍然收到编译器警告,我通过按照建议here 声明类来修复它。
【解决方案2】:

打字稿加载器应该为你修复循环依赖。因此,将其拆分为多个文件应该没问题。 (只需使用导入 typescript loader 即可解决,如 python)。

也就是说,大多数时候这不是一个好主意,并且表明您可能想要抽象某些东西(创建接口并引用 this)或者您有多个类应该只是一个类。

【讨论】:

  • 问题不在 Typescript,问题出在运行时,Typescript 被转译为 javascript。
  • 每当我遇到循环依赖时,我都会做同样的假设,但有时你真的需要它(我确实有一个肮脏的解决方案,但将所有类放在同一个文件中,恕我直言不那么脏!)
【解决方案3】:

好吧,我将我的答案从我发布到的另一个线程中复制粘贴到这里,因为我猜它是相关的,并且之前的答案并没有真正为问题提供简单的解决方案。

通过向基类添加几个新方法和一个额外的类型属性,我设法删除了instanceof 检查并因此删除了循环依赖项(因为如前所述,在编译代码时类型定义被删除)。

例如

class A {
  constructor() {
    this.type = 'A'
  }
  isInstanceOfA(): this is A {
    return this.type === 'A'
  }
  isInstanceOfB(): this is B {
    return this.type === 'B'
  }
}

class B extends A {
  constructor() {
    super()
    this.type = 'B'
  }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-05-28
    • 2016-02-16
    • 1970-01-01
    • 1970-01-01
    • 2018-02-05
    • 1970-01-01
    • 2022-01-09
    • 1970-01-01
    相关资源
    最近更新 更多