Node.js'de döngüsel bağımlılıklarla nasıl başa çıkılır?


162

Son zamanlarda nodejs ile çalışıyorum ve hala açık bir soru ise özür dilerim modül sistemi ile kavramaya başladım. Aşağıdaki gibi kabaca kod istiyorum:

a.js (düğümle çalıştırılan ana dosya)

var ClassB = require("./b");

var ClassA = function() {
    this.thing = new ClassB();
    this.property = 5;
}

var a = new ClassA();

module.exports = a;

b.js

var a = require("./a");

var ClassB = function() {
}

ClassB.prototype.doSomethingLater() {
    util.log(a.property);
}

module.exports = ClassB;

Benim sorunum ClassB örneğinden ClassA örneğine erişemiyorum gibi görünüyor.

İstediğimi elde etmek için modülleri yapılandırmanın doğru / daha iyi bir yolu var mı? Değişkenleri modüller arasında paylaşmanın daha iyi bir yolu var mı?


Sorgu ayırma, gözlemlenebilir model ve daha sonra CS adamlarının yöneticiler dediği komuta bakmanızı öneririm - temel olarak gözlemlenebilir model için bir sarıcı.
dewwwald

Yanıtlar:


86

Node.js döngüsel requirebağımlılıklara izin verirken , oldukça dağınık olabileceğini fark ettiğiniz ve muhtemelen kodunuzu yeniden yapılandırmak için yeniden yapılandırmanız daha iyi olacaktır. Belki ihtiyacınız olanı başarmak için diğer ikisini kullanan üçüncü bir sınıf oluşturun.


6
+1 Bu doğru cevap. Dairesel bağımlılıklar kod kokusudur. A ve B her zaman birlikte kullanılırsa, bunlar etkili bir şekilde tek bir modüldür, bu yüzden birleştirin. Ya da bağımlılığı kırmanın bir yolunu bulun; belki kompozit bir örüntüdür.
James

94
Her zaman değil. veritabanı modellerinde, örneğin, model A ve B'ye sahipsem, model AI'da model B'ye (örneğin, birleştirme işlemlerine) başvurmak isteyebilir ve bunun tersi de geçerlidir. Bu nedenle, "zorunlu" işlevini kullanmadan önce birkaç A ve B özelliğini (diğer modüllere bağlı olmayanlar) dışa aktarmanız daha iyi bir yanıt olabilir.
João Bruno Abou Hatem de Liz

11
Dairesel bağımlılıkları kod kokusu olarak görmüyorum. İhtiyaç duyulan birkaç durumun olduğu bir sistem geliştiriyorum. Örneğin, kullanıcıların birçok takıma ait olabileceği takımları ve kullanıcıları modelleme. Bu yüzden, modellememde bir şeyler yanlış değil. Açıkçası, iki varlık arasındaki dairesel bağımlılığı önlemek için kodumu yeniden düzenleyebilirim, ancak bu etki alanı modelinin en saf şekli olmaz, bu yüzden bunu yapmayacağım.
Alexandre Martini

1
O zaman bağımlılığı gerektiğinde enjekte etmeliyim, demek istediğin bu mu? Döngüsel problemle iki bağımlılık arasındaki etkileşimi kontrol etmek için bir üçüncü kullanmak?
giovannipds

2
Bu dağınık değil .. birisi kod ia tek bir dosyadan kaçınmak için bir dosyayı frenlemek isteyebilirsiniz. Düğümün de belirttiği gibi exports = {}, kodunuzun başına ve ardından kodunuzun exports = yourDatasonuna bir eklemeniz gerekir. Bu uygulama ile dairesel bağımlılıklardan neredeyse tüm hataları önleyeceksiniz.
Prieston

178

module.exportsTamamen değiştirmek yerine özellikleri ayarlamaya çalışın . Örneğin, module.exports.instance = new ClassA()içinde a.js, module.exports.ClassB = ClassBiçinde b.js. Dairesel modül bağımlılıkları yaptığınızda, module.exportstalep eden modül, gerekli modülden eksik olana bir referans alacaktır ; bu, diğer özellikleri ekleyebilir, ancak tamamını ayarladığınızda, module.exportsaslında gerektiren modülün sahip olmadığı yeni bir nesne oluşturursunuz. erişim yolu.


6
Bunların hepsi doğru olabilir, ama yine de dairesel bağımlılıklardan kaçının diyebilirim. Tamamen yüklenmemiş sesler gibi modüller ile başa çıkmak için özel düzenlemeler yapmak, istemediğiniz bir sorun yaratacaktır. Bu cevap, eksik yüklü modüllerle nasıl başa çıkılacağına dair bir çözüm önermektedir ... Bunun iyi bir fikir olduğunu düşünmüyorum.
Alexander Mills

1
module.exportsDiğer sınıfların sınıfın bir örneğini 'oluşturmasına' izin vermek için sınıf yapıcısını tamamen değiştirmeden nasıl yerleştirirsiniz ?
Tim Visée

1
Yapabileceğini sanmıyorum. Modülünüzü almış olan modüller bu değişikliği
göremez

52

[EDIT] 2015 değil ve çoğu kütüphane (yani ekspres) daha iyi kalıplarla güncellemeler yaptı, böylece dairesel bağımlılıklar artık gerekli değil. Onları kullanmamanızı tavsiye ederim .


Ben burada eski bir cevap kazarak biliyorum ... Burada mesele module.exports tanımlanmış olmasıdır sonra sen ClassB gerektirir. (JohnnyHK'nin bağlantısının gösterdiği gibi) Dairesel bağımlılıklar Düğümde harika çalışıyor, sadece senkronize olarak tanımlanıyor. Düzgün kullanıldığında, aslında birçok yaygın düğüm sorununu çözerler app(diğer dosyalardan express.js'ye erişmek gibi )

Dairesel bağımlılığa sahip bir dosyaya ihtiyaç duymadan önce gerekli dışa aktarma işlemlerinin tanımlandığından emin olun .

Bu kırılacak:

var ClassA = function(){};
var ClassB = require('classB'); //will require ClassA, which has no exports yet

module.exports = ClassA;

Bu çalışacak:

var ClassA = module.exports = function(){};
var ClassB = require('classB');

appDiğer modellerde express.js'ye erişmek için her zaman bu kalıbı kullanıyorum :

var express = require('express');
var app = module.exports = express();
// load in other dependencies, which can now require this file and use app

2
kalıbı paylaştığınız ve daha sonra dışa aktarırken bu kalıbı nasıl kullandığınızı daha da paylaştığınız için teşekkür ederizapp = express()
user566245

34

Bazen üçüncü bir sınıfı (JohnnyHK'nın önerdiği gibi) tanıtmak gerçekten yapaydır, bu yüzden Ianzz'a ek olarak: module.exports'u değiştirmek istiyorsanız, örneğin bir sınıf oluşturuyorsanız (b.js dosyası gibi Yukarıdaki örnekte), bu da mümkündür, yalnızca dairesel gereksinimi başlatan dosyada 'module.exports = ...' ifadesinin requir deyiminden önce gerçekleştiğinden emin olun.

a.js (düğümle çalıştırılan ana dosya)

var ClassB = require("./b");

var ClassA = function() {
    this.thing = new ClassB();
    this.property = 5;
}

var a = new ClassA();

module.exports = a;

b.js

var ClassB = function() {
}

ClassB.prototype.doSomethingLater() {
    util.log(a.property);
}

module.exports = ClassB;

var a = require("./a"); // <------ this is the only necessary change

teşekkürler coen, module.exports'un dairesel bağımlılıklar üzerinde bir etkisi olduğunu hiç fark etmemiştim.
Laurent Perrin

bu özellikle Mongoose (MongoDB) modelleri için kullanışlıdır; BlogPost modelinde yorumlara referanslar içeren bir dizi olduğunda ve her Yorum modelinde BlogPost referansı olduğunda bir sorunu çözmeme yardımcı olur.
Oleg Zarevennyi

14

Çözüm, başka bir denetleyiciye ihtiyaç duymadan önce dışa aktarma nesnenizi 'ileriye beyan' etmektir. Yani tüm modüllerinizi bu şekilde yapılandırırsanız ve bunun gibi herhangi bir sorunla karşılaşmazsanız:

// Module exports forward declaration:
module.exports = {

};

// Controllers:
var other_module = require('./other_module');

// Functions:
var foo = function () {

};

// Module exports injects:
module.exports.foo = foo;

3
Aslında bu beni exports.foo = function() {...}bunun yerine kullanmamı sağladı . Kesinlikle hile yaptı. Teşekkürler!
Zanona

Burada ne önerdiğinden emin değilim. module.exportsvarsayılan olarak düz bir Nesne olduğundan, "ileri bildirim" satırınız gereksizdir.
ZachB

7

Minimum değişiklik gerektiren bir çözüm module.exports, geçersiz kılmak yerine genişletiliyor .

a.js - b.js * 'den yöntem kullanan uygulama giriş noktası ve modülü

_ = require('underscore'); //underscore provides extend() for shallow extend
b = require('./b'); //module `a` uses module `b`
_.extend(module.exports, {
    do: function () {
        console.log('doing a');
    }
});
b.do();//call `b.do()` which in turn will circularly call `a.do()`

b.js - a.js yöntemini kullanan modül

_ = require('underscore');
a = require('./a');

_.extend(module.exports, {
    do: function(){
        console.log('doing b');
        a.do();//Call `b.do()` from `a.do()` when `a` just initalized 
    }
})

Çalışacak ve üretecek:

doing b
doing a

Bu kod çalışmazken:

a.js

b = require('./b');
module.exports = {
    do: function () {
        console.log('doing a');
    }
};
b.do();

b.js

a = require('./a');
module.exports = {
    do: function () {
        console.log('doing b');
    }
};
a.do();

Çıktı:

node a.js
b.js:7
a.do();
    ^    
TypeError: a.do is not a function

4
Eğer yoksa underscore, o zaman ES6 en Object.assign()aynı işi yapabilir _.extend()bu yanıtında yapıyor.
joeytwiddle

5

Sadece ihtiyaç duyduğunuzda tembel ihtiyaç duymaya ne dersiniz? Yani b.js'niz aşağıdaki gibi görünüyor

var ClassB = function() {
}
ClassB.prototype.doSomethingLater() {
    var a = require("./a");    //a.js has finished by now
    util.log(a.property);
}
module.exports = ClassB;

Tabii ki tüm zorunlu ifadeleri dosyanın üzerine koymak iyi bir uygulamadır. Ama vardır ben bir başka alakasız modülünün şey seçtiği için kendimi affetmeyeceğim ortamlar. Buna bilgisayar korsanlığı deyin, ancak bu bazen daha fazla bağımlılık getirmek veya fazladan bir modül eklemek veya yeni yapılar (EventEmitter vb.)


Ve bazen bir ebeveyne referansları koruyan alt nesnelerle bir ağaç veri yapısı ile uğraşırken kritiktir. Bahşiş için teşekkürler.
Robert Oschler

5

İnsanların gördükleri başka bir yöntem de ilk satırda dışa aktarma ve bunu yerel bir değişken olarak kaydetmektir:

let self = module.exports = {};

const a = require('./a');

// Exporting the necessary functions
self.func = function() { ... }

Bu yöntemi kullanma eğilimindeyim, bunun herhangi bir dezavantajı var mı?


tercih edebilirsiniz module.exports.func1 = ,module.exports.func2 =
Ashwani Agarwal

4

Bunu kolayca çözebilirsiniz: module.exports'u kullandığınız modüllerde başka bir şeye ihtiyaç duymadan verilerinizi dışa aktarmanız yeterlidir:

classA.js

class ClassA {

    constructor(){
        ClassB.someMethod();
        ClassB.anotherMethod();
    };

    static someMethod () {
        console.log( 'Class A Doing someMethod' );
    };

    static anotherMethod () {
        console.log( 'Class A Doing anotherMethod' );
    };

};

module.exports = ClassA;
var ClassB = require( "./classB.js" );

let classX = new ClassA();

classB.js

class ClassB {

    constructor(){
        ClassA.someMethod();
        ClassA.anotherMethod();
    };

    static someMethod () {
        console.log( 'Class B Doing someMethod' );
    };

    static anotherMethod () {
        console.log( 'Class A Doing anotherMethod' );
    };

};

module.exports = ClassB;
var ClassA = require( "./classA.js" );

let classX = new ClassB();

3

Lanzz ve setect'in cevaplarına benzer şekilde, aşağıdaki kalıbı kullanıyorum:

module.exports = Object.assign(module.exports, {
    firstMember: ___,
    secondMember: ___,
});

Object.assign()Kopyalar içine üyeleri exportszaten diğer modüllere verildi nesne.

=Atama sadece ayar olduğundan, mantıksal olarak gereksiz module.exportskendisine, ama bu benim IDE (WebStorm) tanımasını yardımcı olduğu bunu kullanıyorum firstMember"-> Deklarasyonu Git To" (Cmd-B), bu nedenle bu modülün bir özelliktir ve diğer araçlar diğer dosyalardan çalışacaktır.

Bu desen çok hoş değil, bu yüzden sadece döngüsel bağımlılık sorununun çözülmesi gerektiğinde kullanıyorum.


2

İşte tam olarak bulduğum hızlı bir çözüm.

'A.js' dosyasında

let B;
class A{
  constructor(){
    process.nextTick(()=>{
      B = require('./b')
    })
  } 
}
module.exports = new A();

'B.js' dosyasına aşağıdakini yazın

let A;
class B{
  constructor(){
    process.nextTick(()=>{
      A = require('./a')
    })
  } 
}
module.exports = new B();

Bu şekilde, olay döngüsü sınıflarının bir sonraki yinelemesinde doğru bir şekilde tanımlanacak ve bu zorunlu ifadeler beklendiği gibi çalışacaktır.


1

Aslında sonunda benim bağımlılığımı

 var a = null;
 process.nextTick(()=>a=require("./a")); //Circular reference!

hoş değil, ama işe yarıyor. B.js'yi değiştirmekten daha anlaşılır ve dürüsttür (örneğin, sadece modülleri arttırmak. İhracat), aksi halde olduğu gibi mükemmeldir.


Bu sayfadaki tüm çözümlerden, sorunumu çözen tek çözüm bu. Her birini sırayla denedim.
Joe Lapp

0

Bundan kaçınmanın bir yolu, başka bir dosyaya ihtiyaç duymamaktır, sadece başka bir dosyada ihtiyacınız olan her şeyi bir işleve argüman olarak aktarmaktır. Bu şekilde dairesel bağımlılık asla ortaya çıkmaz.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.