47 votes

Comment résoudre cette dépendance circulaire des modules ES6 ?

EDIT : Pour plus de contexte, voir également la discussion sur ES Discuss.


J'ai trois modules A, B et C. Les modules A et B importent l'export par défaut du module C, et le module C importe l'export par défaut à la fois de A et de B. Cependant, le module C ne dépend pas des valeurs importées de A et de B lors de l'évaluation du module, seulement à un moment donné lors de l'exécution après que les trois modules aient été évalués. Les modules A et B dépendent de la valeur importée de C lors de leur évaluation de module.

Le code ressemble à ceci :

// --- Module A

import C from 'C'
class A extends C {
// ...
}
export {A as default}

.

// --- Module B

import C from 'C'
class B extends C {
// ...
}
export {B as default}

.

// --- Module C

import A from 'A'
import B from 'B'
class C {
constructor() {
// cela peut s'exécuter plus tard, après que les trois modules aient été évalués, ou
// peut-être jamais.
console.log(A)
console.log(B)
}
}
export {C as default}

J'ai le point d'entrée suivant :

// --- Point d'entrée

import A from './app/A'
console.log('Point d'entrée', A)

Cependant, ce qui se passe réellement est que le module B est évalué en premier, et il échoue avec cette erreur dans Chrome (en utilisant des classes ES6 natives, sans transpilage) :

Uncaught TypeError : Class extends value undefined is not a function or null

Cela signifie que la valeur de C dans le module B lors de l'évaluation du module B est undefined car le module C n'a pas encore été évalué.

Vous devriez pouvoir facilement reproduire en créant ces quatre fichiers et en exécutant le fichier d'entrée.

Mes questions sont (puis-je poser deux questions concrètes ?) : Pourquoi l'ordre de chargement est-il ainsi ? Comment les modules à dépendance circulaire peuvent-ils être écrits de manière à ce que le valeur de C lors de l'évaluation de A et de B ne soit pas undefined ?

(Je penserais que l'environnement des modules ES6 pourrait intelligemment découvrir qu'il devra exécuter le corps du module C avant de pouvoir éventuellement exécuter les corps des modules A et B).

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 et NonCircularDep. 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 de A, 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.

0voto

Gili Points 14674

Voici une solution simple qui a fonctionné pour moi. J'ai d'abord essayé l'approche de trusktr mais cela a déclenché des avertissements bizarres d'eslint et d'IntelliJ IDEA (ils affirmaient que la classe n'était pas déclarée alors qu'elle l'était). La solution suivante est intéressante car elle élimine les boucles de dépendance. Pas de magie.

  1. Séparez la classe avec des dépendances circulaires en deux parties : le code qui déclenche la boucle et le code qui ne le fait pas.
  2. Placez le code qui ne déclenche pas de boucle dans un module "interne". Dans mon cas, j'ai déclaré la superclasse et j'ai supprimé toutes les méthodes qui faisaient référence aux sous-classes.
  3. Créez un module accessible au public.
    • import d'abord le module interne.
    • import les modules qui déclenchent la boucle de dépendance.
    • Ajoutez de nouveau les méthodes que nous avons enlevées à l'étape 2.
  4. Demander à l'utilisateur d'importer le module accessible au public.

L'exemple de l'OP est un peu artificiel car ajouter un constructeur à l'étape 3 est beaucoup plus difficile que d'ajouter des méthodes normales, mais le concept général reste le même.

internal/c.js

// Remarquez, nous évitons d'importer des dépendances qui pourraient déclencher des boucles.
// Il est acceptable d'importer des dépendances externes ou internes que nous savons être sûres.

class C {
    // La classe de l'OP n'avait pas de méthodes qui ne déclenchaient
    // pas de boucle, mais si elle en avait, vous les déclareriez ici.
}

export {C as default}

c.js

import C from './internal/c'
// REMARQUE : Nous devons importer './internal/c' en premier !
import A from 'A'
import B from 'B'

// Voir http://stackoverflow.com/a/9267343/14731 pour comprendre pourquoi nous ne pouvons pas remplacer
// directement "C.prototype.constructor".
let temp = C.prototype;
C = function() {
  // cela peut s'exécuter plus tard, après l'évaluation des trois modules, ou
  // éventuellement jamais.
  console.log(A)
  console.log(B)
}
C.prototype = temp;

// Pour les méthodes normales, il suffit d'inclure :
// C.prototype.strippedMethod = function() {...}

export {C as default}

Tous les autres fichiers restent inchangés.

0 votes

Dans votre exemple, C ne dépend que de A et B au moment de l'exécution, mais pas au moment de l'évaluation du module. Et si vous voulez que C dépende de A et B au moment de l'évaluation ? C'est le problème que j'ai rencontré. Par exemple, supposons que nous voulions class C extends (A.name === 'A' ? Foo : Bar) {}. C'est purement hypothétique, imaginez une étape de construction remplaçant A par une définition différente basée sur je-ne-sais-quoi. Le point principal est que la classe C ne peut être définie qu'en fonction de la valeur de A.

0 votes

Les "fonctions d'initialisation" dans le fil de discussion esdiscuss sont une façon de résoudre cela. Sans elles, alors que A dépend de C, et que C dépend de A, l'évaluation d'un module échouera avec C ou A étant indéfini, selon l'ordre dans lequel les modules sont évalués.

0 votes

@trusktr Pouvez-vous s'il vous plaît poster une réponse séparée détaillant à quoi ressemble la solution "init functions" dans ce cas ? Je ne le comprends pas.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X