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

发布于 2025-01-13 08:03:38 字数 1054 浏览 2 评论 0原文

设置:

想象一下以下设置:

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

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

请参阅下面的屏幕截图:

“在此处输入图像描述”"

问题:

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

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

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

目标:

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

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

codesandbox。 io

Setup:

Imagine the following setup:

There is an api that contains let's say a folder foo and bar. These folders export all their public stuff to their local index.ts which will just re-export the public stuff via export * from [...] to make it more convenient.

In my example, there is a circular dependency, because foo.ts requires a part of bar and vice-versa - and I totally understand why this is the case.

See screenshot below:

enter image description here

Question:

How can I resolve this in an environment with hundreds of classes, functions, constants, types, enums, etc. effectively with TypeScript? I imagine that I need some kind of helper file to resolve the commonalities.

Even if I created some kind of foobar folder that requires foo and bar and then exports everything into one big export file it'll probably get messy really soon. What if I need only bar or only foo? Is a named export good enough?

I also want to avoid problems in the future, so I am looking for a robust solution. The call precedence is not the main issue that I try to tackle here. It's more about how to set up the dependencies in a smart way.

Goal:

I'd like to use both foo and bar separately and they should be able to share functions/types/enums/interfaces etc. with each other.

A very simple code snippet can be found here:

codesandbox.io

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

乱世争霸 2025-01-20 08:03:38

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

  1. 如果 foobar 之间存在循环依赖,消除它的最简单方法是将所有循环依赖单元提取到单独的模块/目录中,例如 foobar.你提到了这个解决方案,但是依赖的方向应该是不同的。 foobar 都应该需要 foobar 来提供提取的共享代码。这样您仍然可以单独导入 foobar (暂时导入 foobar)。
  2. 这也适用于您提到的每个 functions/types/enums/interfaces - 如果 foobar 都使用了某些东西,那么应该放置它在单独的模块/目录中。
  3. 关于诸如函数/类型/枚举/接口之类的名称 - 我会考虑使用此类名称作为最后的手段(例如,作为目录树中叶子目录的名称),因为它们通常传达不相关的信息。更好的方法是围绕业务域概念而不是与实现细节相关的名称来组织(并置)代码。这提高了代码的可发现性并降低了代码组织的脆弱性。例如
<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. 保持 foobar 小。尝试将 foobar 中的一些子域/区域/上下文提取到单独的目录中。这应该会生成更小的 index.ts 文件。
  2. 这可能是固执己见且不受欢迎的......考虑避免桶文件。在我看来,如果你发布了一个库,barrel 文件会很好地工作。在这种情况下,您可以精确指定公共成员(假设 CommonJS 模块)。然而,在我看来,目录树中较低级别的桶文件没有提供太多价值。您仍然可以直接导入模块(绕过桶文件)。它们使重构速度变慢,因为在许多情况下您需要手动更新桶文件。如果您忘记更新它们,它们有时会创建一个非常讨厌的循环依赖,在运行时以未定义值的形式表现出来。您可能拥有看起来更干净的导入路径,但实际上很少手动使用导入路径。这是 IDE 的工作。

Your example seems to be quite generic, so I try to describe some general rules of thumbs, but they may be somehow opinionated.

  1. If there is a circular dependency between foo and bar the simplest approach to eliminate it is to extract all circularly dependent units into a separate module/directory e.g. foobar. You mentioned this solution, but the direction of dependencies should be different. Both foo and bar should require foobar that provides the extracted shared code. This way you can still import separately foo and bar (transiently importing foobar).
  2. This applies also to every functions/types/enums/interfaces you mentioned - if something is used by both foo and bar then it should be placed in a separate module/directory.
  3. Regarding names like functions/types/enums/interfaces - I would consider using such names as a last resort (e.g. as names for leaves directories in directories tree), because they communicate information that most often is irrelevant. A much better approach is to organize (colocate) code around business domain concepts instead of names related to implementation details. This increases code discoverability and makes code organization less fragile. e.g. instead of
<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

... consider using:

<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. Keep foo and bar small. Try to distill some sub-domains/areas/contexts from foo or bar into separate directories. This should generate smaller index.ts files.
  2. This one may be opinionated and unpopular... consider avoiding barrel files. In my opinion barrel file works nicely if you publish a library. In such a case you can specify precisely public members (assuming CommonJS module). However, barrel files on lower levels in the directories tree don't provide much value in my opinion. You can still import modules directly (bypassing barrel file). They make refactoring slower because in many cases you need to manually update barrel files. If you forget to update them, they occasionally create a very nasty circular dependency that at the runtime manifests itself in the form of undefined values. You may have imports paths that look cleaner but in practice one very rarely works manually with imports paths. This is a job for the IDE.
寄人书 2025-01-20 08:03:38

很抱歉给大家带来了关于命名的误解。不幸的是,我有机会在实际应用程序中看到类似的名称,并以某种方式错误地认为您也想使用此约定。当谈到“最终要么是所有东西都堆积起来的巨大文件,要么是超级小文件”。这是一个找到良好平衡点的问题。我不介意很多小文件(js 模块),它们专注于单一功能 - 这是一个标志,表明人们已经从一些更大的用例中正确地提取了更小的职责。这会生成更易于理解、测试和维护的代码。大文件(js 模块)或大类/函数通常是 SRP 损坏的标志。关于 sandbox.io 示例 - 我无法理解它,也不理解 helloworld 函数背后的意图。它们只是简单的函数,相互递归调用(导致堆栈溢出)。最简单的重构是仅使用共享函数,例如放置在 foobar 目录中的 buildGreeting(msg1, msg2) 。从 foo 目录导出 const world = 'world',从 bar 目录导出 const hello = 'hello',然后在其他一些同级目录中创建一个带有如下调用的模块:


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

buildGreeting(hello, word);

但是,很难说明此示例代码的任何有意义的改进,因为它没有说明任何实际用例。

Sorry for the misunderstanding about naming. Unfortunately, I had a chance to see similar names in real apps and somehow wrongly assumed that you also want to use this convention. When it comes to "ending up either with huge files that have everything piled up or super-tiny files". This is a matter of finding a good balance. I don't mind a lot of small files (js modules), that are focused on a single functionality - it is a sign that one has correctly distilled smaller responsibilities from some bigger use case. This produces code that is simpler to understand, test and maintain. The big files (js modules) or big classes/functions are often a sign that SRP is broken. Regarding sandbox.io example - I can't wrap my head around it and don't understand the intentions behind hello and world functions. They are just simple functions that recursively call each other (causing stack overflow). The simplest refactor would be to just use a shared function like e.g. buildGreeting(msg1, msg2) placed in foobar directory. Export const world = 'world' from foo directory, and const hello = 'hello' from bar directory, then in some other sibling directory create a module with a call like:


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

buildGreeting(hello, word);

However, it is challenging to illustrate any meaningful improvement over this example code, because it does not illustrate any real use case.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文