RequireJS / AMD로 순환 종속성을 처리하는 방법은 무엇입니까?


80

내 시스템에는 개발 중에 각각 별도의 파일로 브라우저에로드 된 여러 "클래스"가 있으며 프로덕션을 위해 함께 연결되었습니다. 로드되면 G다음 예제와 같이 전역 객체의 속성을 초기화합니다 .

var G = {};

G.Employee = function(name) {
    this.name = name;
    this.company = new G.Company(name + "'s own company");
};

G.Company = function(name) {
    this.name = name;
    this.employees = [];
};
G.Company.prototype.addEmployee = function(name) {
    var employee = new G.Employee(name);
    this.employees.push(employee);
    employee.company = this;
};

var john = new G.Employee("John");
var bigCorp = new G.Company("Big Corp");
bigCorp.addEmployee("Mary");

내 전역 개체를 사용하는 대신 James Burke의 제안 에 따라 각 클래스를 자체 AMD 모듈 로 만드는 것을 고려하고 있습니다 .

define("Employee", ["Company"], function(Company) {
    return function (name) {
        this.name = name;
        this.company = new Company(name + "'s own company");
    };
});
define("Company", ["Employee"], function(Employee) {
    function Company(name) {
        this.name = name;
        this.employees = [];
    };
    Company.prototype.addEmployee = function(name) {
        var employee = new Employee(name);
        this.employees.push(employee);
        employee.company = this;
    };
    return Company;
});
define("main", ["Employee", "Company"], function (Employee, Company) {
    var john = new Employee("John");
    var bigCorp = new Company("Big Corp");
    bigCorp.addEmployee("Mary");
});

문제는 이전에는 Employee와 Company간에 선언 시간 종속성이 없었습니다. 원하는 순서대로 선언을 넣을 수 있었지만 이제 RequireJS를 사용하면 여기에 (의도적으로) 순환되는 종속성이 도입됩니다. 위의 코드는 실패합니다. 물론,에 addEmployee(), 첫 번째 줄을 추가하는 var Employee = require("Employee");그것이 작동되도록 그것은 나를 필요로하지만 RequireJS를 사용하지 않는 열등으로이 솔루션을 참조 / AMD는 개발자가이 새로 생성 된 순환 종속성을 인식하고 그것에 대해 뭔가를 할 수 있습니다.

RequireJS / AMD로이 문제를 해결하는 더 좋은 방법이 있습니까? 아니면 RequireJS / AMD를 설계되지 않은 것에 사용하고 있습니까?

답변:


59

이것은 실제로 AMD 형식의 제한 사항입니다. 내보내기를 사용할 수 있으며 그 문제는 사라집니다. 내보내기가 추악하다고 생각하지만 일반 CommonJS 모듈이 문제를 해결하는 방법입니다.

define("Employee", ["exports", "Company"], function(exports, Company) {
    function Employee(name) {
        this.name = name;
        this.company = new Company.Company(name + "'s own company");
    };
    exports.Employee = Employee;
});
define("Company", ["exports", "Employee"], function(exports, Employee) {
    function Company(name) {
        this.name = name;
        this.employees = [];
    };
    Company.prototype.addEmployee = function(name) {
        var employee = new Employee.Employee(name);
        this.employees.push(employee);
        employee.company = this;
    };
    exports.Company = Company;
});

그렇지 않으면 메시지에서 언급 한 require ( "Employee")도 작동합니다.

일반적으로 모듈의 경우 AMD 여부에 관계없이 순환 종속성에 대해 더 잘 알고 있어야합니다. 일반 JavaScript에서도 예제에서 G 객체와 같은 객체를 사용해야합니다.


3
난 당신처럼 모두 콜백 '인수 목록에서 수출을 선언했다 생각 function(exports, Company)하고 function(exports, Employee). 어쨌든 RequireJS에 감사드립니다.
Sébastien RoccaSerra

@jrburke 중재자 또는 핵심 또는 기타 하향식 구성 요소에 대해 이것이 단방향으로 올바르게 수행 될 수 있다고 생각합니까? 두 가지 방법을 모두 사용하여 액세스 할 수 있도록 만드는 것이 끔찍한 아이디어입니까? stackoverflow.com/questions/11264827/…
SimplGy

1
이것이 어떻게 문제를 해결하는지 이해하지 못합니다. 내 이해는 정의가 실행되기 전에 모든 종속성이로드되어야한다는 것입니다. "exports"가 첫 번째 종속성으로 전달되는 경우는 그렇지 않습니까?
BT

1
함수에서 매개 변수로 내보내기를 놓치지 않습니까?
shabunc 2014 년

1
누락 된 내보내기 매개 변수에 대한 @shabunc의 요점에 대한 후속 조치는 다음 질문을 참조하십시오. stackoverflow.com/questions/28193382/…
Michael.Lumley

15

나는 이것이 (다단계) 순환 종속성이 감지되지 않은 더 큰 프로젝트에서 상당한 단점이라고 생각합니다. 그러나 madge를 사용 하면 순환 종속성 목록을 인쇄하여 접근 할 수 있습니다.

madge --circular --format amd /path/src

CACSVML-13295 : sc-admin-ui-express amills001c $ madge --circular --format amd ./ 순환 종속성이 없습니다!
Alexander Mills

8

시작할 때 종속성을로드 할 필요가없는 경우 (예 : 클래스를 확장 할 때) 다음을 수행 할 수 있습니다. ( http://requirejs.org/docs/api.html# 에서 가져옴 원형 )

파일에서 a.js:

    define( [ 'B' ], function( B ){

        // Just an example
        return B.extend({
            // ...
        })

    });

그리고 다른 파일에서 b.js:

    define( [ ], function( ){ // Note that A is not listed

        var a;
        require(['A'], function( A ){
            a = new A();
        });

        return function(){
            functionThatDependsOnA: function(){
                // Note that 'a' is not used until here
                a.doStuff();
            }
        };

    });

OP의 예에서 다음과 같이 변경됩니다.

    define("Employee", [], function() {

        var Company;
        require(["Company"], function( C ){
            // Delayed loading
            Company = C;
        });

        return function (name) {
            this.name = name;
            this.company = new Company(name + "'s own company");
        };
    });

    define("Company", ["Employee"], function(Employee) {
        function Company(name) {
            this.name = name;
            this.employees = [];
        };
        Company.prototype.addEmployee = function(name) {
            var employee = new Employee(name);
            this.employees.push(employee);
            employee.company = this;
        };
        return Company;
    });

    define("main", ["Employee", "Company"], function (Employee, Company) {
        var john = new Employee("John");
        var bigCorp = new Company("Big Corp");
        bigCorp.addEmployee("Mary");
    });

2
Gili가 그의 의견에서 말했듯이,이 해결책은 잘못되었으며 항상 작동하지 않을 것입니다. 코드 블록이 먼저 실행되는 경쟁 조건이 있습니다.
Louis Ameline 2015

6

순환 종속성에 대한 문서를 살펴 보았습니다. http://requirejs.org/docs/api.html#circular

a 및 b에 순환 종속성이있는 경우 다음과 같이 모듈에서 require를 종속성으로 추가하도록 모듈에 말합니다.

define(["require", "a"],function(require, a) { ....

그런 다음 "a"가 필요할 때 다음과 같이 "a"를 호출하십시오.

return function(title) {
        return require("a").doSomething();
    }

이것은 나를 위해 일했습니다.


5

나는 순환 의존성을 피할 것입니다. 아마도 다음과 같습니다.

G.Company.prototype.addEmployee = function(employee) {
    this.employees.push(employee);
    employee.company = this;
};

var mary = new G.Employee("Mary");
var bigCorp = new G.Company("Big Corp");
bigCorp.addEmployee(mary);

이 문제를 해결하고 순환 종속성을 유지하는 것은 좋은 생각이 아니라고 생각합니다. 일반적인 나쁜 습관처럼 느껴집니다. 이 경우 내 보낸 함수가 호출 될 때 실제로 해당 모듈이 필요하기 때문에 작동 할 수 있습니다. 그러나 실제 정의 함수 자체에서 모듈이 필요하고 사용되는 경우를 상상해보십시오. 어떤 해결 방법도 작동하지 않습니다. 이것이 아마도 require.js가 정의 함수의 종속성에서 순환 종속성 감지에 빠르게 실패하는 이유 일 것입니다.

해결 방법을 추가해야하는 경우 더 깨끗한 IMO는 제때에 (이 경우 내 보낸 함수에서) 종속성을 요구하는 것입니다. 그러면 정의 함수가 제대로 실행됩니다. 그러나 더 깨끗한 IMO는 순환 종속성을 완전히 피하는 것이므로 귀하의 경우에는 정말 쉽게 할 수 있습니다.


2
requirejs 도구가 지원하지 않기 때문에 도메인 모델을 단순화하고 덜 사용하도록 제안합니다. 도구는 개발자의 삶을 더 쉽게 만들어줍니다. 도메인 모델은 매우 간단합니다-직원과 회사. 직원 개체는 자신이 일하는 회사를 알아야하며 회사에는 직원 목록이 있어야합니다. 도메인 모델은 여기에 실패 도구의 오른쪽이다
Dethariel

5

게시 된 모든 답변 ( https://stackoverflow.com/a/25170248/14731 제외 )이 잘못되었습니다. 공식 문서 (2014 년 11 월 현재)조차 잘못되었습니다.

나를 위해 일한 유일한 해결책은 "게이트 키퍼"파일을 선언하고 순환 종속성에 의존하는 모든 방법을 정의하는 것입니다. 구체적인 예는 https://stackoverflow.com/a/26809254/14731 을 참조 하십시오 .


위의 솔루션이 작동하지 않는 이유는 다음과 같습니다.

  1. 다음을 할 수 없습니다.
var a;
require(['A'], function( A ){
     a = new A();
});

를 사용 a하는 코드 블록보다 먼저이 코드 블록이 실행된다는 보장이 없기 때문에 나중에 사용합니다 a. (이 솔루션은 90 %의 시간 동안 작동하기 때문에 잘못된 것입니다.)

  1. exports동일한 경쟁 조건에 취약하지 않다고 믿을 이유 가 없습니다.

이에 대한 해결책은 다음과 같습니다.

//module A

    define(['B'], function(b){

       function A(b){ console.log(b)}

       return new A(b); //OK as is

    });


//module B

    define(['A'], function(a){

         function B(a){}

         return new B(a);  //wait...we can't do this! RequireJS will throw an error if we do this.

    });


//module B, new and improved
    define(function(){

         function B(a){}

       return function(a){   //return a function which won't immediately execute
              return new B(a);
        }

    });

이제 모듈 C에서이 모듈 A와 B를 사용할 수 있습니다.

//module C
    define(['A','B'], function(a,b){

        var c = b(a);  //executes synchronously (no race conditions) in other words, a is definitely defined before being passed to b

    });

btw, 여전히 문제가 있다면 @yeahdixon의 대답이 정확해야하며 문서 자체가 정확하다고 생각합니다.
Alexander Mills

귀하의 방법론이 작동한다는 데 동의하지만 문서가 정확하고 "동기식"에 한 걸음 더 가까울 수 있습니다.
Alexander Mills

모든 변수가로드시 설정되기 때문에 가능합니다. 사용자가 시간 여행자가 아니라면 버튼이 존재하기 전에 클릭하십시오. 인과 관계를 깨고 경쟁 조건이 가능합니다.
Eddie

0

제 경우에는 "단순한"개체의 코드를 더 복잡한 개체로 이동하여 순환 종속성을 해결했습니다. 저에게 그것은 컬렉션이자 모델 클래스였습니다. 귀하의 경우 회사의 Employee 특정 부분을 Employee 클래스에 추가 할 것 같습니다.

define("Employee", ["Company"], function(Company) {
    function Employee (name) {
        this.name = name;
        this.company = new Company(name + "'s own company");
    };
    Company.prototype.addEmployee = function(name) {
        var employee = new Employee(name);
        this.employees.push(employee);
        employee.company = this;
    };

    return Employee;
});
define("Company", [], function() {
    function Company(name) {
        this.name = name;
        this.employees = [];
    };
    return Company;
});
define("main", ["Employee", "Company"], function (Employee, Company) {
    var john = new Employee("John");
    var bigCorp = new Company("Big Corp");
    bigCorp.addEmployee("Mary");
});

약간 해키하지만 간단한 경우에는 작동합니다. addEmployeeEmployee를 매개 변수로 사용하도록 리팩터링 하는 경우 종속성이 외부인에게 훨씬 더 분명해야합니다.

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