requirejs/amdで円形の依存関係を処理する方法は?
-
28-10-2019 - |
質問
私のシステムでは、開発中にブラウザにそれぞれ別のファイルにロードされた多くの「クラス」があり、生産のために一緒に連結しています。それらがロードされると、彼らはここでグローバルオブジェクトのプロパティを初期化します 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");
自分のグローバルオブジェクトを使用する代わりに、各クラスを自分のクラスにすることを検討しています 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");
});
問題は、以前には従業員と会社の間に宣言時の依存関係がなかったことです。宣言をどんな順序にも置くことができますが、今ではrequirejsを使用して、これは依存関係を導入します。上記のコードは失敗します。もちろん、で addEmployee()
, 、最初の行を追加します var Employee = require("Employee");
します それを機能させます, 、しかし、このソリューションは、開発者である私がこの新しく作成された円形依存関係を認識し、それについて何かをすることを要求するため、requirejs/amdを使用しないより劣っていると考えています。
requirejs/amdでこの問題を解決するより良い方法はありますか、それとも設計されていないものにrequirejs/amdを使用していますか?
解決
これは確かにAMD形式の制限です。エクスポートを使用でき、その問題はなくなります。エクスポートはugいだと思いますが、それは定期的な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;
});
それ以外の場合、メッセージで言及する要件(「従業員」)も機能します。
一般に、モジュールを使用すると、AMDであろうとなかろうと、円形の依存関係をより意識する必要があります。プレーンJavaScriptであっても、例ではGオブジェクトのようなオブジェクトを必ず使用する必要があります。
他のヒント
これは、(マルチレベルの)円形の依存関係が検出されない大規模なプロジェクトでは、非常に欠点だと思います。ただし、 マッジ 円形の依存関係のリストを印刷して、それらにアプローチできます。
madge --circular --format amd /path/src
最初に依存関係をロードする必要がない場合(たとえば、クラスを拡張しているとき)、これはあなたができることです:(から取られた http://requirejs.org/docs/api.html#circular)
ファイル内 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);
この問題を回避し、円形の依存を維持しようとすることは良い考えではないと思います。一般的な悪い練習のように感じます。この場合、エクスポートされた関数が呼び出されたときにそれらのモジュールが本当に必要なため、機能する可能性があります。ただし、モジュールが必要であり、実際の定義関数自体で使用される場合を想像してください。回避策はそれを機能させません。それがおそらく、定義関数の依存関係における円形の依存関係検出でjsが高速に失敗する理由です。
実際に回避策を追加する必要がある場合、クリーナーのIMOは、すぐに依存関係を必要とすることです(この場合はエクスポートされた関数で)、定義関数は正常に実行されます。しかし、よりきれいなIMOでさえ、循環依存関係を完全に避けるためだけのものであり、あなたのケースでは本当に簡単だと感じています。
投稿されたすべての回答(を除く https://stackoverflow.com/a/25170248/14731) 間違っている。公式文書(2014年11月現在)でさえ間違っています。
私のために働いた唯一の解決策は、「ゲートキーパー」ファイルを宣言し、円形の依存関係に依存する方法を定義することです。見る https://stackoverflow.com/a/26809254/14731 具体的な例について。
上記のソリューションが機能しない理由は次のとおりです。
- それはいけません:
var a;
require(['A'], function( A ){
a = new A();
});
そして、使用します a
後で、このコードブロックが使用するコードブロックの前に実行されるという保証がないため a
. 。 (このソリューションは90%の時間で動作するため誤解を招くものです)
- 私はそれを信じる理由はないと思います
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
});
円形の依存関係に関するドキュメントを見ました。http://requirejs.org/docs/api.html#circular
aとbの循環依存性がある場合、モジュールには、次のようなモジュールに依存関係として必要なものを追加することが記載されています。
define(["require", "a"],function(require, a) { ....
次に、「a」を「a」と呼びます。
return function(title) {
return require("a").doSomething();
}
これは私のために働いた
私の場合、「より単純な」オブジェクトのコードをより複雑なオブジェクトに移動することにより、円形の依存関係を解決しました。私にとってそれはコレクションとモデルクラスでした。あなたの場合、従業員固有の部分を従業員クラスに追加すると思います。
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");
});
少しハッキーですが、単純なケースのために機能するはずです。そして、あなたがリファクタリングする場合 addEmployee
従業員をパラメーターにするために、依存関係は部外者にとってさらに明白でなければなりません。