webentwicklung-frage-antwort-db.com.de

Wie kann diese zirkulare Abhängigkeit des ES6-Moduls behoben werden?

BEARBEITEN: Weitere Hintergrundinformationen finden Sie in der -Diskussion zu ES Discuss .


Ich habe drei Module A, B und C. A und B importieren den Standardexport aus dem Modul C und das Modul C importiert den Standard sowohl aus A als auch aus B. Das Modul C hängt jedoch nicht von den Werten ab, die während der Modulbewertung aus A und B importiert wurden, nur zur Laufzeit, nachdem alle drei Module ausgewertet wurden. Die Module A und Bdo hängen von dem Wert ab, der während der Modulbewertung aus C importiert wurde.

Der Code sieht ungefähr so ​​aus:

// --- 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() {
        // this may run later, after all three modules are evaluated, or
        // possibly never.
        console.log(A)
        console.log(B)
    }
}

export {C as default}

Ich habe den folgenden Einstiegspunkt:

// --- Entrypoint

import A from './app/A'
console.log('Entrypoint', A)

Was jedoch tatsächlich passiert ist, dass das Modul B zuerst ausgewertet wird und mit diesem Fehler in Chrome fehlschlägt (unter Verwendung nativer ES6-Klassen, nicht mit Transpiling):

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

Dies bedeutet, dass der Wert von C in Modul B bei der Auswertung von Modul Bundefined ist, da das Modul C noch nicht ausgewertet wurde.

Sie sollten sich leicht reproduzieren lassen, indem Sie diese vier Dateien erstellen und die Einstiegspunktdatei ausführen.

Meine Fragen sind (kann ich zwei konkrete Fragen haben?): Warum ist die Ladereihenfolge so? Wie können die zirkular abhängigen Module so geschrieben werden, dass sie funktionieren, sodass der Wert von C bei der Auswertung von A und B nicht undefined ist?

(Ich denke, die Umgebung des ES6-Moduls kann in der Lage sein, intelligent zu erkennen, dass der Rumpf des Moduls C ausgeführt werden muss, bevor er die Rumpfmodule A und B ausführen kann.)

31
trusktr

Ich würde empfehlen, die Inversion der Kontrolle zu verwenden. Machen Sie Ihren C-Konstruktor rein, indem Sie einen A- und einen B-Parameter wie folgt hinzufügen: 

// --- Module A

import C from './C';

export default class A extends C {
    // ...
}

// --- Module B

import C from './C'

export default class B extends C {
    // ...
}

// --- Module C

export default class C {
    constructor(A, B) {
        // this may run later, after all three modules are evaluated, or
        // possibly never.
        console.log(A)
        console.log(B)
    }
}

// --- Entrypoint

import A from './A';
import B from './B';
import C from './C';
const c = new C(A, B);
console.log('Entrypoint', C, c);
document.getElementById('out').textContent = 'Entrypoint ' + C + ' ' + c;

https://www.webpackbin.com/bins/-KlDeP9Rb60MehsCMa8u

Aktualisieren Sie als Antwort auf diesen Kommentar: Wie kann diese zirkulare Abhängigkeit des ES6-Moduls behoben werden?

Wenn Sie nicht möchten, dass der Bibliotheksverbraucher verschiedene Implementierungen kennt, können Sie alternativ eine andere Funktion/Klasse exportieren, die diese Details verbirgt:

// Module ConcreteCImplementation
import A from './A';
import B from './B';
import C from './C';
export default function () { return new C(A, B); }

oder verwende dieses Muster:

// --- Module A

import C, { registerA } from "./C";

export default class A extends C {
  // ...
}

registerA(A);

// --- Module B

import C, { registerB } from "./C";

export default class B extends C {
  // ...
}

registerB(B);

// --- Module C

let A, B;

const inheritors = [];

export const registerInheritor = inheritor => inheritors.Push(inheritor);

export const registerA = inheritor => {
  registerInheritor(inheritor);
  A = inheritor;
};

export const registerB = inheritor => {
  registerInheritor(inheritor);
  B = inheritor;
};

export default class C {
  constructor() {
    // this may run later, after all three modules are evaluated, or
    // possibly never.
    console.log(A);
    console.log(B);
    console.log(inheritors);
  }
}

// --- Entrypoint

import A from "./A";
import B from "./B";
import C from "./C";
const c = new C();
console.log("Entrypoint", C, c);
document.getElementById("out").textContent = "Entrypoint " + C + " " + c;

Aktualisieren Sie als Antwort auf diesen Kommentar: Wie kann diese zirkulare Abhängigkeit des ES6-Moduls behoben werden?

Um dem Endbenutzer zu ermöglichen, eine beliebige Teilmenge der Klassen zu importieren, erstellen Sie einfach eine lib.js-Datei, die die nach außen gerichtete api exportiert:

import A from "./A";
import B from "./B";
import C from "./C";
export { A, B, C };

oder: 

import A from "./A";
import B from "./B";
import C from "./ConcreteCImplementation";
export { A, B, C };

Dann kannst du:

// --- Entrypoint

import { C } from "./lib";
const c = new C();
const output = ["Entrypoint", C, c];
console.log.apply(console, output);
document.getElementById("out").textContent = output.join();
3
msand

Es gibt eine andere mögliche Lösung.

// --- Entrypoint

import A from './app/A'
setTimeout(() => console.log('Entrypoint', A), 0)

Ja, es ist ein widerlicher Hack, aber es funktioniert

1
Jon Wyatt

Sie können es lösen, indem Sie Module dynamisch laden

Ich hatte das gleiche Problem und ich importiere nur Module dynamisch.

Import nach Bedarf ersetzen:

import module from 'module-path';

mit dynamisch importieren:

let module;
import('module-path').then((res)=>{
    module = res;
});

In Ihrem Beispiel sollten Sie c.js folgendermaßen ändern:

import C from './internal/c'
let A;
let B;
import('./a').then((res)=>{
    A = res;
});
import('./b').then((res)=>{
    B = res;
});

// See http://stackoverflow.com/a/9267343/14731 for why we can't replace "C.prototype.constructor"
let temp = C.prototype;
C = function() {
  // this may run later, after all three modules are evaluated, or
  // possibly never.
  console.log(A)
  console.log(B)
}
C.prototype = temp;

export {C as default}

Weitere Informationen zum dynamischen Importieren:

http://2ality.com/2017/01/import-operator.html

Es gibt einen anderen Weg, dies mit leo zu erklären nur für ECMAScript 2019:

https://stackoverflow.com/a/40418615/1972338

Um die zirkuläre Abhängigkeit zu analysieren, Artur Hebda erkläre es hier:

https://railsware.com/blog/2018/06/27/how-to-analyze-circular-dependencies-in-es6/

0
Mehdi Yeganeh

Folgendes habe ich in meiner eigenen Bibliothek verwendet:

  1. Deklarieren Sie "interne" Module, die Klassen ohne zirkuläre Abhängigkeiten deklarieren.
  2. Deklarieren Sie öffentlich zugängliche Module, die interne Module in der richtigen Reihenfolge laden, und fügen Sie alle Methoden hinzu, die Zirkelabhängigkeiten referenzieren müssen.
  3. Bitten Sie den Benutzer, eines der öffentlich zugänglichen Module zu importieren

intern/a.js

import C from './internal/c'

class A extends C {
    // ...
}

export {A as default}

intern/b.js

import C from './internal/c'

class B extends C {
    // ...
}

export {B as default}

intern/c.js

class C {
}

export {C as default}

c.js

import C from './internal/c'
import A from './a'
import B from './b'

// See http://stackoverflow.com/a/9267343/14731 for why we can't replace "C.prototype.constructor"
let temp = C.prototype;
C = function() {
  // this may run later, after all three modules are evaluated, or
  // possibly never.
  console.log(A)
  console.log(B)
}
C.prototype = temp;

export {C as default}

a.js

import './c.js'
import './internal/a.js'

export {A as default}

b.js

import './c.js'
import './internal/b.js'

export {B as default}

Einstiegspunkt

import A from './app/a'
console.log('Entrypoint', A)
0
Gili