La réponse est d'utiliser les "fonctions init". Pour référence, regardez les deux messages qui commencent ici : https://esdiscuss.org/topic/how-to-solve-this-basic-es6-module-circular-dependency-problem#content-21
La solution ressemble à ceci :
// --- Module A
import C, {initC} from './c';
initC();
console.log('Module A', C)
class A extends C {
// ...
}
export {A as default}
-
// --- Module B
import C, {initC} from './c';
initC();
console.log('Module B', C)
class B extends C {
// ...
}
export {B as default}
-
// --- Module C
import A from './a'
import B from './b'
var C;
export function initC(){
if (C) return;
C = class C {
constructor() {
console.log(A)
console.log(B)
}
}
}
initC();
export {C as default}; // IMPORTANT: not `export default C;` !!
-
// --- Entrypoint
import A from './A'
console.log('Entrypoint', new A) // runs the console.logs in the C
constructor.
Voir également ce fil de discussion pour des informations connexes : https://github.com/meteor/meteor/issues/7621#issuecomment-238992688
Il est important de noter que les exportations sont hissées (cela peut être étrange, vous pouvez demander dans esdiscuss pour en savoir plus) tout comme var
mais le levage se fait à travers les modules. Les classes ne peuvent pas être hissées, mais les fonctions peuvent l'être (tout comme dans les scopes normaux pré-ES6, mais à travers les modules parce que les exportations sont des liaisons vivantes qui atteignent d'autres modules éventuellement avant d'être évaluées, presque comme s'il y avait un scope qui englobe tous les modules où les identifiants ne peuvent être accédés que par l'utilisation de import
).
Dans cet exemple, le point d'entrée importe du module A
qui importe du module C
qui importe du module B
. Cela signifie que le module B
sera évalué avant le module C
mais en raison du fait que l'exporté initC
fonction du module C
est hissé, le module B
recevra une référence à ce houblon. initC
et donc le module B
appel appel initC
avant le module C
est évaluée.
Cela entraîne le var C
variable du module C
pour être défini avant le class B extends C
définition. Magique !
Il est important de noter que le module C
doit utiliser var C
pas const
ou let
sinon une erreur de zone morte temporelle devrait théoriquement être déclenchée dans un véritable environnement ES6. Par exemple, si le module C ressemble à
// --- Module C
import A from './a'
import B from './b'
let C;
export function initC(){
if (C) return;
C = class C {
constructor() {
console.log(A)
console.log(B)
}
}
}
initC();
export {C as default}; // IMPORTANT: not `export default C;` !!
puis dès que le module B
appelle initC
une erreur sera déclenchée et l'évaluation du module échouera.
var
est hissé dans le cadre du module C
Il est donc disponible lorsque initC
s'appelle. C'est un excellent exemple d'une raison pour laquelle vous voudriez vraiment utiliser var
au lieu de let
ou const
dans un environnement ES6+.
Cependant, vous pouvez noter que le rollup ne gère pas cela correctement. https://github.com/rollup/rollup/issues/845 et un hack qui ressemble à let C = C
peut être utilisé dans certains environnements, comme indiqué dans le lien ci-dessus vers le problème de Meteor.
Une dernière chose importante à noter est la différence entre export default C
et export {C as default}
. La première version n'est pas exporter le C
variable du module C
comme une liaison vivante, mais par valeur. Ainsi, lorsque export default C
est utilisé, la valeur de var C
est undefined
et sera assigné à une nouvelle variable var default
qui est caché à l'intérieur de la portée du module ES6, et en raison du fait que C
est assigné sur default
(dans le mot anglais var default = C
par valeur, alors chaque fois que l'exportation par défaut du module C
est accessible par un autre module (par exemple le module B
), l'autre module s'insère dans le module C
et en accédant à la valeur de la default
qui sera toujours undefined
. Ainsi, si le module C
utilise export default C
alors même si le module B
appelle initC
(qui fait modifier les valeurs du module C
interne de l'entreprise C
variable), module B
n'accèdera pas réellement à ce système interne C
il accédera à la variable default
variable, qui est toujours undefined
.
Cependant, lorsque le module C
utilise la forme export {C as default}
le système de modules ES6 utilise l'option C
comme variable exportée par défaut plutôt que de créer une nouvelle variable interne. default
variable. Cela signifie que la C
est une liaison vivante. Chaque fois qu'un module dépendant d'un module C
est évalué, il recevra le module C
interne de l'entreprise C
à ce moment précis, non pas par valeur, mais presque comme si on transmettait la variable à l'autre module. Ainsi, lorsque le module B
appelle initC
, module C
interne de l'entreprise C
est modifiée, et le module B
est capable de l'utiliser car il a une référence à la même variable (même si l'identifiant local est différent) ! En fait, à tout moment au cours de l'évaluation du module, lorsqu'un module utilise l'identifiant qu'il a importé d'un autre module, le système du module accède à l'autre module et obtient la valeur à ce moment précis.
Je parie que la plupart des gens ne connaissent pas la différence entre export default C
et export {C as default}
Dans la plupart des cas, ils n'en auront pas besoin, mais il est important de connaître la différence lorsqu'on utilise des "live bindings" sur des modules avec des "init functions" afin de résoudre les dépendances circulaires, entre autres choses où les live bindings peuvent être utiles. Sans vouloir m'éloigner du sujet, si vous avez un singleton, les liaisons vivantes peuvent être utilisées comme un moyen de faire en sorte que la portée d'un module soit l'objet singleton, et les liaisons vivantes la façon dont les choses du singleton sont accédées.
Une façon de décrire ce qui se passe avec les liaisons en direct est d'écrire du javascript qui se comporterait comme l'exemple de module ci-dessus. Voici ce que les modules B
et C
pourrait ressembler d'une manière qui décrit les "liens vivants" :
// --- Module B
initC()
console.log('Module B', C)
class B extends C {
// ...
}
// --- Module C
var C
function initC() {
if (C) return
C = class C {
constructor() {
console.log(A)
console.log(B)
}
}
}
initC()
Cela montre efficacement ce qui se passe dans la version du module ES6 : B est évalué en premier, mais var C
et function initC
sont hissés à travers les modules, de sorte que le module B
est capable d'appeler initC
et ensuite utiliser C
tout de suite, avant var C
et function initC
sont rencontrés dans le code évalué.
Bien sûr, cela devient plus compliqué lorsque les modules utilisent des identifiants différents, par exemple si le module B
a import Blah from './c'
alors Blah
sera toujours une liaison vivante avec le C
variable du module C
mais cela n'est pas très facile à décrire en utilisant un levage variable normal comme dans l'exemple précédent, et en fait Rollup ne le gère pas toujours correctement .
Supposons par exemple que nous ayons un module B
comme les suivants et les modules A
et C
sont les mêmes :
// --- Module B
import Blah, {initC} from './c';
initC();
console.log('Module B', Blah)
class B extends Blah {
// ...
}
export {B as default}
Si nous utilisons du JavaScript pour décrire uniquement ce qui se passe avec les modules B
et C
le résultat serait le suivant :
// --- Module B
initC()
console.log('Module B', Blah)
class B extends Blah {
// ...
}
// --- Module C
var C
var Blah // needs to be added
function initC() {
if (C) return
C = class C {
constructor() {
console.log(A)
console.log(B)
}
}
Blah = C // needs to be added
}
initC()
Une autre chose à noter est que le module C
a également le initC
appel de fonction. C'est juste au cas où le module C
est toujours évaluée en premier, il n'y a pas de mal à l'initialiser à ce moment-là.
Et la dernière chose à noter est que dans ces exemples, les modules A
et B
dépendent de C
au moment de l'évaluation du module mais pas au moment de l'exécution. Lorsque les modules A
et B
sont évalués, puis exigent pour le C
l'exportation à définir. Cependant, lorsque le module C
est évaluée, elle ne dépend pas de A
et B
les importations étant définies. Module C
n'auront besoin que d'utiliser A
et B
au moment de l'exécution dans le futur, après que tous les modules aient été évalués, par exemple lorsque le point d'entrée s'exécute new A()
qui exécutera le C
constructeur. C'est pour cette raison que le module C
n'a pas besoin initA
ou initB
fonctions.
Il est possible que plusieurs modules dans une dépendance circulaire doivent dépendre les uns des autres, et dans ce cas, une solution plus complexe de "fonction d'initialisation" est nécessaire. Par exemple, supposons que le module C
veut console.log(A)
pendant le temps d'évaluation du module avant class C
est défini :
// --- Module C
import A from './a'
import B from './b'
var C;
console.log(A)
export function initC(){
if (C) return;
C = class C {
constructor() {
console.log(A)
console.log(B)
}
}
}
initC();
export {C as default}; // IMPORTANT: not `export default C;` !!
En raison du fait que le point d'entrée dans l'exemple du haut importe A
le C
sera évalué avant le module A
module. Cela signifie que console.log(A)
en haut du module C
enregistrera undefined
parce que class A
n'a pas encore été défini.
Enfin, pour faire fonctionner le nouvel exemple de manière à ce qu'il enregistre class A
au lieu de undefined
Si le module B est modifié, l'exemple entier devient encore plus compliqué (j'ai laissé de côté le module B et le point d'entrée, car ils ne changent pas) :
// --- Module A
import C, {initC} from './c';
initC();
console.log('Module A', C)
var A
export function initA() {
if (A) return
initC()
A = class A extends C {
// ...
}
}
initA()
export {A as default} // IMPORTANT: not `export default A;` !!
-
// --- Module C
import A, {initA} from './a'
import B from './b'
initA()
var C;
console.log(A) // class A, not undefined!
export function initC(){
if (C) return;
C = class C {
constructor() {
console.log(A)
console.log(B)
}
}
}
initC();
export {C as default}; // IMPORTANT: not `export default C;` !!
Maintenant, si le module B
voulait utiliser A
pendant le temps d'évaluation, les choses deviendraient encore plus compliquées, mais je vous laisse imaginer cette solution...
4 votes
Ah, j'ai voulu cette question en tant que question canonique depuis longtemps, voyons quand j'aurai le temps de répondre à tout.
0 votes
Joe, je vois que tu as posté une solution sur esdiscuss.org/topic/… mais je ne comprends pas à quoi font référence
CircularDep
etNonCircularDep
. Pour moi, tous les modules dans la question contiennent une forme de dépendances circulaires. Pourrais-tu s'il te plaît poster une réponse en termes deA
,B
,C
tels que définis dans cette question?0 votes
@Gili Hey, si tu peux répondre dans ce fil de discussion, ce serait génial. Je pense que pour ce faire, il te suffit d'envoyer un e-mail avec le même sujet.