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


162

私は最近nodejsを使用していて、モジュールシステムを理解しているので、これが明らかな質問である場合は謝罪してください。大体以下のようなコードが欲しい:

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のインスタンスにアクセスできないことです。

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


コマンドクエリの分離、監視可能なパターン、そしてCSの連中がマネージャーと呼ぶものを調べることをお勧めします。これは基本的に監視可能なパターンのラッパーです。
dewwwald 2017年

回答:


86

node.jsは循環require依存関係を許可しますが、ご存知のようにかなり面倒であり、おそらくコードを再構築してそれを必要としない方がよいでしょう。おそらく、他の2つを使用して必要なことを実行する3番目のクラスを作成します。


6
+1これが正解です。循環依存はコードのにおいです。AとBが常に一緒に使用される場合、それらは事実上単一モジュールなので、それらをマージします。または、依存関係を解除する方法を見つけます。多分それは複合パターンです。
ジェームズ

94
常にではない。たとえば、データベースモデルでは、モデルAとBがある場合、モデルAIではモデルBを参照する必要があります(たとえば、操作を結合するため)。逆も同様です。したがって、「require」関数を使用する前に、いくつかのAおよびBプロパティ(他のモジュールに依存しないプロパティ)をエクスポートする方が適切な場合があります。
ジョアン・ブルーノアボハテム・デ・リズ

11
また、循環依存関係がコードのにおいとして認識されません。必要なケースがいくつかあるシステムを開発しています。たとえば、ユーザーが多くのチームに所属できるチームとユーザーのモデリング。だから、私のモデリングに問題があるということではありません。明らかに、2つのエンティティ間の循環依存を回避するためにコードをリファクタリングすることはできますが、これはドメインモデルの最も純粋な形式ではないので、それを行いません。
Alexandre Martini

1
次に、必要なときに依存関係を注入する必要がありますが、それはあなたが意味することですか?3番目を使用して、循環問題のある2つの依存関係間の相互作用を制御しますか?
giovannipds 2017年

2
これは厄介なことではありません。誰かがコードの本、つまり単一のファイルを回避するためにファイルをブレーキングしたい場合があります。ノードが提案するようexports = {}に、コードの先頭にを追加してから、コードexports = yourDataの末尾にを追加する必要があります。この方法を使用すると、循環依存によるほとんどすべてのエラーを回避できます。
プリーストン2018年

178

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


6
これはすべて本当かもしれませんが、循環依存関係を回避することをお勧めします。サウンドが完全にロードされていないモジュールに対処するために特別な調整を行うと、不要な将来の問題が発生します。この回答は、不完全にロードされたモジュールを処理する方法の解決策を規定しています...それは良い考えではないと思います。
Alexander Mills

1
module.exports他のクラスがクラスのインスタンスを「構築」できるようにするには、クラスコンストラクターを完全に置き換えずにどのように配置しますか?
TimVisée16年

1
できないと思います。モジュールをすでにインポートしたモジュールは、その変更を確認できません
lanzz

52

[編集]それは2015年ではなく、ほとんどのライブラリー(つまり、エクスプレス)は、より良いパターンで更新を行ったので、循環依存は必要なくなりました。単に使用しないことをお勧めします


私はここで古い答えを掘り起こしていることを知っています...ここでの問題は、ClassBが必要になった後に module.exportsが定義されることです。(JohnnyHKのリンクに示されています)循環依存はNodeでうまく機能し、同期的に定義されるだけです。適切に使用すると、実際に多くの一般的なノードの問題(app他のファイルからのexpress.jsへのアクセスなど)が解決されます

循環依存関係のあるファイルが必要になる前に、必要なエクスポートが定義されていることを確認してください。

これは壊れます:

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

module.exports = ClassA;

これは動作します:

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

app他のファイルのexpress.js にアクセスするには、常にこのパターンを使用します。

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

2
うちエクスポートするときは、一般的にこのパターンを使用する方法のパターンとし、さらに共有を共有していただきありがとうございますapp = express()
user566245を

34

Ianzzに加えて、(JohnnyHKが助言するように)3番目のクラスを導入するのが本当に人為的である場合があります。たとえば、module.exportsを置き換えたい場合(たとえば、クラスを作成している場合(上記の例)、これも可能です。循環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

おかげで、私はmodule.exportsが循環依存に影響を与えることに気づかなかった。
Laurent Perrin

これは、Mongoose(MongoDB)モデルで特に役立ちます。BlogPostモデルにコメントへの参照を持つ配列があり、各CommentモデルにBlogPostへの参照がある場合の問題の修正に役立ちます。
Oleg Zarevennyi

14

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

// 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
実際、これはexports.foo = function() {...}代わりに単に使用することにしました。間違いなくトリックをしました。ありがとう!
zanona 2016年

ここで何を提案しているのかわかりません。module.exportsデフォルトではすでにプレーンオブジェクトであるため、「前方宣言」の行は冗長です。
ZachB

7

最小限の変更を必要とするソリューションは、それ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

4
がない場合underscore、ES6 はこの回答で行っているのObject.assign()と同じ作業を行うことができます_.extend()
joeytwiddle 2017年

5

必要なときだけ怠惰を必要とするのはどうですか?だからあなたの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ステートメントをファイルの上に置くことをお勧めします。しかし、そこにある、私はそうでない場合は無関係なモジュールから何かを取り出すために自分を許す場面が。これをハックと呼びますが、依存関係を追加したり、モジュールを追加したり、新しい構造(EventEmitterなど)を追加したりするよりも良い場合があります。


また、親への参照を維持する子オブジェクトを含むツリーデータ構造を処理する場合に重要になることがあります。先端をありがとう。
ロバートオシュラー

5

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

let self = module.exports = {};

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

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

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


あなたではなく行うことができmodule.exports.func1 = module.exports.func2 =
Ashwani Agarwalさん

4

これは簡単に解決できます。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

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

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

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

=それだけで設定されているので割り当ては、論理的に冗長であるmodule.exports自分自身に、私はそれを使用しています、それはそれを認識するために私のIDE(WebStorm)を助けるのでfirstMember、このモジュールのプロパティなので、「移動] - >宣言」(CMD-B)他のツールは他のファイルから機能します。

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


2

これは、私が使いきったことがわかった簡単な回避策です。

ファイル「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

実際、私は私の依存関係を必要としました

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

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


このページのすべてのソリューションの中で、これが私の問題を解決した唯一のソリューションです。順番に試してみました。
Joe Lapp、2017年

0

これを回避する1つの方法は、あるファイルを別のファイルに要求せず、別のファイルで必要なものを関数の引数として渡すことです。このようにして、循環依存は発生しません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.