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

답변:


86

node.js는 순환 require종속성을 허용하지만 꽤 지저분 할 수 있으며 필요하지 않도록 코드를 재구성하는 것이 좋습니다. 다른 두 클래스를 사용하여 필요한 것을 달성하는 세 번째 클래스를 만들 수도 있습니다.


6
+1 정답입니다. 순환 종속성은 코드 냄새입니다. A와 B가 항상 함께 사용되면 사실상 단일 모듈이므로 병합하십시오. 또는 의존성을 깨는 방법을 찾으십시오. 복합 패턴 일 수도 있습니다.
James

94
항상 그런 것은 아닙니다. 예를 들어, 모델 A 및 B가있는 경우 데이터베이스 모델에서 모델 AI에서 모델 B를 참조 (예 : 조인 작업)하거나 그 반대의 경우를 원할 수 있습니다. 따라서 "필수"기능을 사용하기 전에 여러 A 및 B 속성 (다른 모듈에 의존하지 않는 속성)을 내보내는 것이 더 좋은 대답 일 수 있습니다.
João Bruno Abou Hatem de Liz

11
또한 순환 종속성을 코드 냄새로 보지 않습니다. 몇 가지 경우가 필요한 시스템을 개발 중입니다. 예를 들어, 사용자가 여러 팀에 속할 수있는 모델링 팀 및 사용자. 따라서 모델링에 문제가있는 것은 아닙니다. 분명히 두 엔터티 간의 순환 종속성을 피하기 위해 코드를 리팩터링 할 수는 있지만 이것이 가장 순수한 형태의 도메인 모델이 아니므로 그렇게하지 않을 것입니다.
Alexandre Martini

1
그렇다면 필요할 때 의존성을 주입해야합니까? 순환 문제와 두 종속성 간의 상호 작용을 제어하기 위해 세 번째를 사용합니까?
giovannipds

2
이것은 지저분하지 않습니다. 누군가 코드 파일 하나를 피하기 위해 파일을 제동하고 싶을 수도 있습니다. 노드가 제안한 것처럼 exports = {}코드 상단에 코드를 추가 한 다음 코드 exports = yourData끝에 추가해야합니다. 이 방법을 사용하면 순환 종속성의 거의 모든 오류를 피할 수 있습니다.
사제

178

속성을 module.exports완전히 바꾸는 대신에 속성을 설정하십시오 . 예, module.exports.instance = new ClassA()a.js, module.exports.ClassB = ClassBb.js. 원형 모듈 의존성을 만들 때 필요한 모듈은 module.exports필요한 모듈에서 불완전한 참조를 가져옵니다.이 속성은 나중에 다른 속성을 추가 할 수 있지만 전체를 설정 module.exports하면 실제로는 필요한 모듈이없는 새 객체를 만듭니다 접근하는 방법.


6
이것은 모두 사실 일 수 있지만 여전히 순환 종속성을 피하십시오. 소리가 불완전하게로드 된 모듈을 처리하기 위해 특별한 준비를하면 원하지 않는 미래의 문제가 발생합니다. 이 답변은 불완전하게로드 된 모듈을 처리하는 방법에 대한 해결책을 규정합니다 ... 나는 그것이 좋은 생각이라고 생각하지 않습니다.
Alexander Mills

1
module.exports다른 클래스가 클래스의 인스턴스를 '구성'할 수 있도록 클래스 생성자를 완전히 바꾸지 않고 어떻게 클래스 생성자를 넣을 수 있습니까?
Tim Visée

1
나는 당신이 할 수 있다고 생각하지 않습니다. 모듈을 이미 가져온 모듈은 해당 변경 사항을 볼 수 없습니다.
lanzz

52

[편집] 2015 년이 아니며 대부분의 라이브러리 (즉, 표현)가 더 나은 패턴으로 업데이트되어 순환 종속성이 더 이상 필요하지 않습니다. 나는 단순히 사용하지 않는 것이 좋습니다 .


나는 여기에 오래된 대답을 파고 있다는 것을 안다 ... 여기서 문제는 ClassB가 필요한 후에 module.exports가 정의된다는 것입니다 . (JohnnyHK의 링크가 보여주는) 순환 종속성은 노드에서 훌륭하게 작동하며 동기식으로 정의됩니다. 올바르게 사용하면 실제로 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와 함께 세 번째 클래스를 소개하는 것이 때로는 인위적인 경우도 있습니다. Ianzz와 함께 : 예를 들어 b.js 파일 같은 위의 예), 이것은 가능합니다. 순환 요구를 시작하는 파일에서 '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 모델에 주석에 대한 참조가있는 배열이 있고 각 주석 모델에 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

여기에서 무엇을 제안하고 있는지 잘 모르겠습니다. module.exports기본적으로 이미 일반 객체이므로 "전달 선언"줄은 중복됩니다.
ZachB

7

최소한의 변경이 필요한 솔루션은이 module.exports를 재정의하는 대신 확장 됩니다.

a.js-b.js에서 메소드를 사용하는 앱 진입 점 및 모듈 *

_ = 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에서 메소드를 사용하는 모듈

_ = 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
이 없으면 underscoreES6 이이 답변에서 Object.assign()수행하는 것과 동일한 작업을 수행 할 수 있습니다 _.extend().
joeytwiddle

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 등)를 추가하는 것보다 낫습니다.


그리고 때로는 부모에 대한 참조를 유지하는 자식 개체로 트리 데이터 구조를 처리 할 때 중요합니다. 팁 고마워.
Robert Oschler 19

5

사람들이 본 다른 방법은 첫 번째 줄에서 내보내고 다음과 같이 로컬 변수로 저장하는 것입니다.

let self = module.exports = {};

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

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

나는이 방법을 사용하는 경향이있다. 그 단점을 알고 있는가?


당신은 오히려 할 수있는 module.exports.func1 = ,module.exports.func2 =
Ashwani 아가 왈에게

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

다음에 이벤트 루프 클래스를 반복 할 때이 방법이 올바르게 정의되고 요구 사항 명령문이 예상대로 작동합니다.


1

실제로 나는 의존성을 요구하게되었습니다.

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

예쁘지 않지만 작동합니다. b.js를 변경하는 것 (예 : modules.export 기능 보강)보다 이해하기 쉽고 정직합니다.


이 페이지의 모든 솔루션 중에서 이것이 내 문제를 해결 한 유일한 솔루션입니다. 나는 차례로 차례로 시도했다.
Joe Lapp

0

그것을 피하는 한 가지 방법은 다른 파일을 요구하지 않고 다른 파일에서 필요한 것을 함수의 인수로 전달하는 것입니다. 이런 식으로 순환 의존성은 절대 발생하지 않습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.