ES6模块和循环依赖问题

8

我在Babel环境下遇到了ES6的问题:

// A.js
class A {
}
export default new A();

// B.js
import C from './C';
class B {
}
export default new B();

// C.js
import A from './A';
import B from './B';
class C {
    constructor(A, B){
        this.A = A;
        this.B = B; // undefined
    }
}
export default new C(A, B)

我这样导入它们:

// stores/index.js
import A from './A';
import B from './B';
import C from './C';

export {
    A,
    B,
    C
}

而从我的应用程序入口点开始,我执行以下操作:

import * as stores from './stores'; 

我本希望执行顺序是A->B->C,但实际上它是A->C->B。
这是因为模块B导入了C,因此在B模块之前评估了C模块。这在设置C时会创建问题,因为在那种情况下B将是未定义的
我看到了类似的问题,但我不确定init函数是否是最佳解决方案,它似乎有点hacky。 : 在ES6中解决这种循环依赖的最佳实践是什么,可能适用于不同的环境(Babel、Rollup)?

期望执行顺序为A -> B -> C - 针对哪个入口点?如果C甚至没有被使用,为什么要在B中导入它? - Estus Flask
它被使用,这是一个缺少实现细节的例子。我已经更新了问题,添加了入口点。 - Leonardo
1
省略细节会改变一切。这里不会出现循环依赖,因为未使用的导入将被跳过。考虑提供 http://stackoverflow.com/help/mcve 。同时仅用于单例的类是反模式。 - Estus Flask
new C() 中的 AB 是未定义的,因为您没有向构造函数传递任何参数,而不是因为导入未起作用。 - Bergi
@Leonardo 如果你想要真正的单例模式,只需使用对象字面量 - 不需要 classnew 和原型继承。或者更好的方法是使用多个命名导出。那些 class 实例甚至不是真正的单例模式,它们可以被重新实例化。如果你想要这样做,最好明确地导出类本身 - 并在你的测试、主应用程序脚本等中实例化它。 - Bergi
显示剩余2条评论
1个回答

16
在ES6中,如何处理循环依赖问题?首先应该尽可能避免使用循环依赖。如果无法完全避免,可以限制涉及的模块只使用具有循环依赖的函数声明,并且永远不要使用导入的值来初始化顶层值(常量、变量、类),包括extends引用。在不同的环境(比如Babel,Rollup)中,模块解析和初始化的顺序在ES6规范中已经定义,因此应该在所有的ES6环境中都是相同的,无论模块如何加载以及它们的标识符如何解析。如果存在循环依赖关系X -> Y -> Z -> … -> X -> …,您需要确定一个起始点。例如,需要先加载X,尽管它依赖于Y,因此需要确保X永远不会使用任何导入值,直到圆中的所有模块都完全初始化。所以你需要在XY之间打破循环,并在Y处开始导入链--它将递归遍历依赖项,直到停在X上,这是没有进一步依赖项正在得到初始化的第一个模块,因此它将作为第一个被评估的模块。您需要遵循的规则是,在导入任何圆中的其他模块之前,始终先导入Y。如果您甚至一次也没有使用此公共单个入口点进入该圆,那么您的卡牌堆将崩溃。在您的特定示例中,这意味着index.js将需要使用
import A from './A';
import C from './C'; // entry point to circular dependencies
import B from './B';
…

如果我们正在编译为ESM - 这很重要吗? - httpete
@httpete 当你编写ESM时很重要。否则你会从哪里进行编译呢? - Bergi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接