web-dev-qa-db-ja.com

Node.jsの循環依存関係を処理する方法

私は最近nodejsを使っていますが、モジュールシステムを理解しているので、これが明らかな質問であればおologiesびします。以下のようなコードが必要です。

a.js(ノードで実行されるメインファイル)

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;

私の問題は、ClassBのインスタンス内からClassAのインスタンスにアクセスできないことです。

私が望むものを達成するためにモジュールを構造化する正しい/より良い方法はありますか?モジュール間で変数を共有するより良い方法はありますか?

144
Runcible

Node.jsは循環require依存関係を許可しますが、ご存じのとおり pretty messy である可能性があり、おそらくコードを再構築して不要にした方がよいでしょう。他の2つを使用して必要なことを達成する3つ目のクラスを作成することもできます。

74
JohnnyHK

プロパティを完全に置き換えるのではなく、module.exportsにプロパティを設定してください。たとえば、a.jsmodule.exports.instance = new ClassA()module.exports.ClassB = ClassBb.jsなど。循環モジュールの依存関係を作成すると、必要なモジュールは、必要なモジュールから不完全なmodule.exportsへの参照を取得します。これは、後で他のプロパティを追加できますが、module.exports必要なモジュールがアクセスする方法を持たない新しいオブジェクトを作成します。

165
lanzz

(JohnnyHKが助言するように)3番目のクラスを導入するのは本当に人工的な場合があるため、Ianzzに加えて:module.exportsを置き換えたい場合、たとえばクラスを作成する場合(b.jsファイルなど)上記の例)、これも可能です。循環requireを開始するファイルで、 'module.exports = ...'ステートメントがrequireステートメントの前に発生することを確認してください。

a.js(ノードで実行されるメインファイル)

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
30
Coen

解決策は、他のコントローラーを必要とする前にエクスポートオブジェクトを「前方宣言」することです。したがって、このようにすべてのモジュールを構造化し、そのような問題に遭遇しない場合:

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

};

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

// Functions:
var foo = function () {

};

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

最小限の変更が必要なソリューションは、module.exportsをオーバーライドする代わりに拡張することです。

a.js-b.js *からdoメソッドを使用するアプリエントリポイントとモジュール

_ = 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からdoメソッドを使用するモジュール

_ = 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 
    }
})

動作し、生成されます:

doing b
doing a

このコードは機能しませんが:

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();

出力:

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

必要なときにだけ必要なレイジーはどうですか?したがって、b.jsは次のようになります

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

もちろん、すべてのrequireステートメントをファイルの先頭に置くことをお勧めします。しかし、そこにはareの場合があります。それ以外の場合は無関係なモジュールから何かを選ぶことを許します。それをハックと呼びますが、これはさらに依存関係を追加したり、余分なモジュールを追加したり、新しい構造(EventEmitterなど)を追加したりするよりも良い場合があります

5
zevero

私が見た他の方法は、最初の行でエクスポートし、次のようなローカル変数として保存することです:

let self = module.exports = {};

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

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

私はこの方法を使用する傾向がありますが、その欠点について知っていますか?

4
Bence Gedai

これは簡単に解決できます。module.exportsを使用するモジュールで他の何かが必要になる前にデータをエクスポートするだけです。

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
Giuseppe Canale

実際に私は私の依存関係を要求することになりました

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

きれいではありませんが、動作します。 b.jsを変更する(たとえば、modules.exportのみを拡張する)場合よりも理解しやすく、正直です。

1
zevero

ここに私が使い切っていることがわかった簡単な回避策があります。

ファイル「a.js」

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

「b.js」ファイルに次のように書きます

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

イベントループクラスの次の反復でこのように正しく定義され、それらのrequireステートメントが期待どおりに機能します。

1

Lanzzとsetectの答えと同様に、私は次のパターンを使用しています。

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

Object.assign()は、すでに他のモジュールに与えられているexportsオブジェクトにメンバーをコピーします。

=の割り当ては、module.exportsをそれ自体に設定するだけなので論理的に冗長ですが、IDE(WebStorm)がfirstMemberがプロパティであることを認識するのに役立つので使用していますこのモジュールの「移動->宣言」(Cmd-B)およびその他のツールは、他のファイルから機能します。

このパターンはあまりきれいではないので、周期的な依存関係の問題を解決する必要がある場合にのみ使用します。

1
joeytwiddle