Programing

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

crosscheck 2020. 10. 15. 07:32
반응형

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


내 시스템에는 개발 중에 각각 별도의 파일로 브라우저에로드 된 여러 "클래스"가 있으며 프로덕션을 위해 함께 연결됩니다. 로드되면 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가 설계되지 않은 것을 사용하고 있습니까?


이것은 실제로 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 객체와 같은 객체를 사용해야합니다.


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

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

시작할 때 종속성을로드 할 필요가없는 경우 (예 : 클래스를 확장 할 때) 다음을 수행 할 수 있습니다. ( 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");
    });

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

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는 순환 종속성을 완전히 피하는 것이므로 귀하의 경우에는 정말 쉽게 할 수 있습니다.


All the posted answers (except https://stackoverflow.com/a/25170248/14731) are wrong. Even the official documentation (as of November 2014) is wrong.

The only solution that worked for me is to declare a "gatekeeper" file, and have it define any method that depends on the circular dependencies. See https://stackoverflow.com/a/26809254/14731 for a concrete example.


Here is why the above solutions will not work.

  1. You cannot:
var a;
require(['A'], function( A ){
     a = new A();
});

and then use a later on, because there is no guarantee that this code block will get executed before the code block that uses a. (This solution is misleading because it works 90% of the time)

  1. I see no reason to believe that exports is not vulnerable to the same race condition.

the solution to this is:

//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);
        }

    });

now we can use these modules A and B in module C

//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

    });

I looked at the docs on circular dependencies :http://requirejs.org/docs/api.html#circular

If there is a circular dependency with a and b , it says in your module to add require as a dependency in your module like so :

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

then when you need "a" just call "a" like so:

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

This worked for me


In my case I solved the circular dependency by moving the code of the "simpler" object into the more complex one. For me that was a collection and a model class. I guess in your case I would add the Employee-specific parts of Company into the Employee class.

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");
});

A bit hacky, but it should work for simple cases. And if you refactor addEmployee to take an Employee as parameter, the dependency should be even more obvious to outsiders.

참고URL : https://stackoverflow.com/questions/4881059/how-to-handle-circular-dependencies-with-requirejs-amd

반응형