网站搜索

javascript - 如何解决 TypeScript 中的循环依赖


设置 :

想象以下设置:

有一个 API,假设包含一个文件夹 foo 和 bar。这些文件夹将它们所有的公共内容导出到它们各自的本地 index.ts 中,然后通过 export * from [...] 重新导出这些公共内容,以使其更方便使用。

在我的示例中,存在循环依赖,因为 foo.ts 需要 bar 的一部分,反之亦然——我完全理解为什么会这样。

请参见下方截图:

问题:

在一个有数百个类、函数、常量、类型、枚举等的环境中,我如何才能有效地用 TypeScript 解决这个问题?我想我需要某种辅助文件来解决这些共性问题。

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

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

目标:

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

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

codesandbox.io

答案#1

你的例子似乎相当通用,所以我尝试描述一些一般的经验法则,但它们可能在某种程度上带有个人观点。

  1. 如果 foo 和 bar 之间存在循环依赖,消除它的最简单方法是将所有循环依赖的单元提取到一个单独的模块/目录中,例如 foobar。你提到了这个解决方案,但依赖方向应该不同。foo 和 bar 都应该依赖 foobar,它提供提取出来的共享代码。这样,你仍然可以分别导入 foo 和 bar(暂时导入 foobar)。
  2. 这同样适用于你提到的每一个 functions/types/enums/interfaces——如果某个东西被 foo 和 bar 都使用,那么它应该被放在一个单独的模块/目录中。
  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

... 考虑使用:

<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
  1. 保持 foo 和 bar 小。尝试从 foo 或 bar 提炼一些子域/区域/上下文到单独的目录中。这样可以生成更小的 index.ts 文件。
  2. 这一点可能是有个人观点且不受欢迎的……考虑避免使用桶文件。在我看来,如果你发布一个库,桶文件效果不错。在这种情况下,你可以精确指定公共成员(假设使用 CommonJS 模块)。然而,在目录树的较低层级使用桶文件,我认为并没有太大价值。你仍然可以直接导入模块(绕过桶文件)。它们会使重构变得更慢,因为在很多情况下你需要手动更新桶文件。如果忘记更新它们,它们有时会在运行时产生非常难处理的循环依赖,表现为 undefined 值。你可能会得到看起来更简洁的导入路径,但实际上很少会手动处理导入路径。这是 IDE 的工作。

答案#2

对于命名方面的误解很抱歉。不幸的是,我曾有机会在真实应用中看到类似的命名方式,因此不知为何错误地以为你也想使用这种约定。至于“最终要么得到包含所有内容的超大文件,要么得到超小的文件”,这是一个寻找良好平衡的问题。我不介意很多小文件(js 模块),只要它们专注于单一功能——这表明你已经从较大的用例中正确地提炼出了较小的职责。这会产生更容易理解、测试和维护的代码。大文件(js 模块)或大类/函数通常意味着单一职责原则(SRP)被破坏了。 关于 sandbox.io 示例——我无法理解它,也不明白 hello 和 world 函数背后的意图。它们只是简单的函数,彼此递归调用(导致堆栈溢出)。最简单的重构方法是使用一个共享函数,例如放在 foobar 目录下的 buildGreeting(msg1, msg2)。从 foo 目录导出 const world='world',从 bar 目录导出 const hello='hello',然后在另一个兄弟目录中创建一个模块,并调用如下:

import {hello} from '../foo'
import {word} from '../bar'

buildGreeting(hello, word);

然而,很难在这段示例代码上展示任何有意义的改进,因为它并没有展示任何实际的用例。

相关文章