简体   繁体   中英

ES6 Modules and Circular Dependency

I'm having this problem in ES6 in a Babel Environment:

// 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)

I import them like this:

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

export {
    A,
    B,
    C
}

And from my app entry point I do:

import * as stores from './stores'; 

I would (hoped) expected the order of execution to be A -> B ->C but in reality it is A-> C-> B.
This is due to module B importing C, so that the C module is evaluated before the B module. This creates a problem in the setup of C because in that case B would be undefined .

I've seen a similar question but I'm not sure the init function is the best solution here, it seems a bit hacky.

Q : What's the best practise about solving this kind of circular dependencies in ES6 that would possibly work in different environments (Babel, Rollup)?

What's the best practice about circular dependencies in ES6?

Avoid them altogether. If you cannot (or don't want to) completely avoid them, restrict the involved modules to only use function declarations with circular dependencies, never initialise top-level values (constants, variables, classes) by using imported values (that includes extends references).

What would possibly work in different environments (Babel, Rollup)?

The order of module resolution and initialisation is defined in the ES6 specification, so this should be the same in all ES6 environments - regardless how the modules are loaded and how their identifiers are resolved.

How to solve this kind of circular dependencies setup?

If you do have a circular dependency X -> Y -> Z -> … -> X -> … , you need to establish a start point. Say you want X to be loaded first, although it depends on Y , so you need to make sure that X never uses any of the imported values until all modules in the circle are completely initialised. So you break the circle between X and Y , and you will need to start the import chain at Y - it will recursively traverse the dependencies until it stops at X , which has no further dependencies that are not already getting initialised, so it will be the first module to be evaluated.

The rule you have to follow then is to always import Y before importing any of the other modules in the circle. If you even once do not use this common single entry point to the circle, your house of cards will collapse.

In your particular example, this means that index.js will need to use

import A from './A';
import C from './C'; // entry point to circular dependencies
import B from './B';
…

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM