Как работать с циклическими зависимостями в Node.js
Я работал с nodejs в последнее время и все еще получаю доступ к модульной системе, поэтому извиняюсь, если это очевидный вопрос. Я хочу код примерно следующим образом:
a.js (основной файл работает с node)
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;
Моя проблема заключается в том, что я не могу получить доступ к экземпляру ClassA из экземпляра ClassB.
Есть ли правильный/лучший способ структурирования модулей для достижения того, что я хочу?
Есть ли лучший способ обмена переменными между модулями?
Ответы
Ответ 1
В то время как node.js разрешает круговые зависимости require
, поскольку вы обнаружили, что это может быть довольно грязно, и вам, вероятно, лучше провести реструктуризацию код не нужен. Возможно, создайте третий класс, который использует два других, чтобы выполнить то, что вам нужно.
Ответ 2
Попробуйте установить свойства на module.exports
вместо того, чтобы полностью его заменить. Например, module.exports.instance = new ClassA()
в a.js
, module.exports.ClassB = ClassB
в b.js
. Когда вы выполняете круговые зависимости модуля, требуемый модуль получит ссылку на неполный module.exports
из требуемого модуля, в который вы можете добавить другие свойства, но при установке всего module.exports
вы фактически создаете новый объект к которому не нужен доступный модуль.
Ответ 3
[EDIT] это не 2015 год, и большинство библиотек (т.е. экспресс) сделали обновления с лучшими шаблонами, поэтому круговые зависимости больше не нужны. Я рекомендую просто не использовать их.
Я знаю, что я выкапываю старый ответ здесь...
Проблема здесь в том, что module.exports определяется после того, как вам требуется ClassB.
(что показывает ссылка JohnnyHK)
Круговые зависимости отлично работают в Node, они просто определяются синхронно.
При правильном использовании они фактически решают множество общих проблем node (например, обращение к express.js app
из других файлов)
Просто убедитесь, что ваш необходимый экспорт определен до того, как вам понадобится файл с циклической зависимостью.
Это сломается:
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');
Я использую этот шаблон все время для доступа к express.js app
в других файлах:
var express = require('express');
var app = module.exports = express();
// load in other dependencies, which can now require this file and use app
Ответ 4
Иногда действительно искусственно вводить третий класс (как советует JohnnyHK), поэтому в дополнение к Ianzz:
Если вы хотите заменить module.exports, например, если вы создаете класс (например, файл b.js в приведенном выше примере), это также возможно, просто убедитесь, что в файле, который запускает round require, оператор 'module.exports =...' встречается перед оператором require.
a.js (основной файл работает с node)
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
Ответ 5
Решение заключается в том, чтобы "переслать объявление" вашего объекта экспорта, прежде чем требовать какой-либо другой контроллер. Поэтому, если вы структурируете все свои модули, как это, и вы не столкнетесь с такими проблемами:
// Module exports forward declaration:
module.exports = {
};
// Controllers:
var other_module = require('./other_module');
// Functions:
var foo = function () {
};
// Module exports injects:
module.exports.foo = foo;
Ответ 6
Решение, требующее минимального изменения, расширяется 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
Ответ 7
Как насчет ленивых, требующих только тогда, когда вам нужно? Итак, ваш 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;
Конечно, хорошая практика заключается в том, чтобы все запросы требовать поверх файла. Но бывают случаи, когда я прощаю себя за то, что выбрал что-то из другого, не связанного с ним модуля. Назовите это взломом, но иногда это лучше, чем введение дополнительной зависимости, или добавление дополнительного модуля или добавление новых структур (EventEmitter и т.д.).
Ответ 8
Другой метод, который я видел, это делать, экспортируя в первой строке и сохраняя его как локальную переменную следующим образом:
let self = module.exports = {};
const a = require('./a');
// Exporting the necessary functions
self.func = function() { ... }
Я использую этот метод, вы знаете о каких-либо его недостатках?
Ответ 9
Вы можете легко решить эту проблему: просто экспортируйте свои данные, прежде чем требовать что-либо еще в модулях, где вы используете 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();
Ответ 10
Подобно ответам lanzz и setect, я использовал следующий шаблон:
module.exports = Object.assign(module.exports, {
firstMember: ___,
secondMember: ___,
});
Object.assign()
копирует элементы в объект exports
, который уже был передан другим модулям.
Назначение =
логически избыточно, поскольку оно просто устанавливает module.exports
для себя, но я использую его, потому что он помогает моей среде IDE (WebStorm) распознавать, что firstMember
является свойством этого модуля, поэтому "Перейти к → Декларация" (Cmd-B) и другие инструменты будут работать из других файлов.
Этот шаблон не очень красив, поэтому я использую его только тогда, когда необходимо решить проблему с циклической зависимостью.
Ответ 11
Вообще-то я в конечном итоге нуждался в моей зависимости от
var a = null;
process.nextTick(()=>a=require("./a")); //Circular reference!
не очень, но он работает. Это более понятно и честно, чем изменение b.js(например, только увеличение modules.export), что в противном случае является совершенным как есть.
Ответ 12
Вот быстрый обходной путь, который я нашел полным.
В файле "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 будут работать как положено.
Ответ 13
для вашей проблемы, вы можете использовать объявления функций.
класс-b.js:
var ClassA = require('./class-a')
module.exports = ClassB
function ClassB() {
}
класс-a.js:
var classB = require('./class-b')
module.exports = ClassA
function ClassA() {
}