如何解决打字稿中的循环依赖

F. *_*ler 4 javascript architecture dependencies circular-dependency typescript

设置:

想象一下以下设置:

有一个 api,其中包含一个文件夹foobar. 这些文件夹将所有公共内容导出到本地文件夹index.ts,本地文件夹将通过重新导出公共内容export * from [...]以使其更加方便。

在我的示例中,存在循环依赖,因为foo.ts需要一部分bar,反之亦然 -我完全理解为什么会出现这种情况

请参阅下面的屏幕截图:

在此输入图像描述

问题:

如何使用 TypeScript 在具有数百个类、函数、常量、类型、枚举等的环境中有效地解决此问题?我想我需要某种帮助文件来解决共性。

即使我创建了某种foobar需要的文件夹foobar然后将所有内容导出到一个大导出文件中,它可能很快就会变得混乱。如果我只需要bar或仅需要怎么办foo?命名导出是否足够好?

我也想避免将来出现问题,所以我正在寻找一个可靠的解决方案。调用优先级不是我在这里尝试解决的主要问题。更多的是关于如何以智能的方式设置依赖关系。

目标:

我想分别使用 foo 和 bar,它们应该能够彼此共享函数/类型/枚举/接口等。

可以在这里找到一个非常简单的代码片段:

Codesandbox.io

rob*_*k86 5

你的例子似乎很通用,所以我尝试描述一些一般的经验法则,但它们可能在某种程度上是固执己见的。

  1. 如果之间存在循环依赖,foo消除bar它的最简单方法是将所有循环依赖单元提取到单独的模块/目录中,例如foobar。你提到了这个解决方案,但是依赖的方向应该是不同的。和foobar应该要求foobar提供提取的共享代码。这样您仍然可以单独导入foobar暂时导入foobar)。
  2. 这也适用于functions/types/enums/interfaces您提到的每一个 - 如果两者都使用某些东西foobar那么它应该放置在单独的模块/目录中。
  3. 关于诸如functions/types/enums/interfaces- 我会考虑使用此类名称作为最后的手段(例如,作为目录树中叶子目录的名称),因为它们传达的信息通常是不相关的。更好的方法是围绕业务域概念而不是与实现细节相关的名称来组织(并置)代码。这提高了代码的可发现性并降低了代码组织的脆弱性。例如代替
<sourcesRoot>/api/functions/getUser.ts
<sourcesRoot>/api/functions/getPosts.ts
<sourcesRoot>/api/enums/UserRole.ts
<sourcesRoot>/api/types/User.ts
<sourcesRoot>/api/types/Post.ts
<sourcesRoot>/api/index.ts
Run Code Online (Sandbox Code Playgroud)

...考虑使用:

<sourcesRoot>/users/api/getUser.ts
<sourcesRoot>/users/model/User.ts
<sourcesRoot>/users/model/UserRole.ts
<sourcesRoot>/users/index.ts

<sourcesRoot>/posts/api/getPosts.ts
<sourcesRoot>/posts/model/Post.ts
<sourcesRoot>/posts/index.ts
Run Code Online (Sandbox Code Playgroud)
  1. 保持foobar。尝试从单独的目录中提取一些子域/区域/上下文foo或将bar其提取到单独的目录中。这应该会生成较小的index.ts文件。
  2. 这可能是固执己见且不受欢迎的......考虑避免桶文件。在我看来,如果你发布一个库,barrel 文件会很好地工作。在这种情况下,您可以精确指定公共成员(假设 CommonJS 模块)。然而,在我看来,目录树中较低级别的桶文件没有提供太多价值。您仍然可以直接导入模块(绕过桶文件)。它们使重构速度变慢,因为在许多情况下您需要手动更新桶文件。如果您忘记更新它们,它们有时会创建一个非常令人讨厌的循环依赖关系,该依赖关系在运行时以未定义值的形式表现出来。您可能拥有看起来更干净的导入路径,但实际上很少手动使用导入路径。这是 IDE 的工作。