【问题标题】:Access imported module in top level scope of file with 'circular' dependency?访问具有“循环”依赖关系的顶级文件范围中的导入模块?
【发布时间】:2017-10-31 17:00:38
【问题描述】:

我有以下模块结构:

/components
├── Button.js
├── Checkbox.js
├── index.js
├── DateSelect
    ├── DateSelect.js
    └── index.js

/components/DateSelect/index.js:

import DateSelect from './DateSelect';

export default DateSelect;

/components/index.js:

import DateSelect from './DateSelect';
import Button from './Button';
import Checkbox from './Checkbox';

export {
  DateSelect,
  Button,
  Checkbox,
};

还有/components/DateSelect/DateSelect.js:

import { Checkbox } from '../';
// ...code

// I want to do this!
const MyCustomCheckbox = props => <Checkbox style={someStyle} />;

// ...code
class DateSelect extends React.Component {
  // code
}
export default DateSelect;

现在,我想在文件的顶级范围内访问Checkbox,就像上面的代码一样,但我得到了undefined。但是,如果我在DateSelectrender 方法中访问此变量,它会按预期工作。

我不完全确定为什么会这样,或者我该如何解决这个问题(我可以做到import Checkbox from '../Checkbox',但我不想更改使用目录的index.js 文件的模式),我也想确切地了解发生了什么。有什么想法吗?

【问题讨论】:

  • 为什么Checkbox.js 依赖于Dateselect.js?你能把它的代码贴出来吗?
  • @Bergi 当然,给我几分钟去我的电脑,但是复选框不依赖于 DateSelect,我谈到的循环依赖是在 DateSelect.js 和 components/index.js 之间,相互导入模块。
  • 嗯,这反过来使Checkbox 也依赖于导入的DateselectAvoid such circular references!直接导入Button 模块即可。
  • 是的@Bergi 是对的——直接导入Checkbox。使用index 模式非常适合定义内部 API 和维护严格的模块边界,但您不能从它导入的组件中引用它。如果您想坚持索引模式,请将Checkbox 文件分解为一个迷你模块/components/CheckBox/Checkbox.js/components/CheckBox/index.js,然后从该索引文件中导入。
  • 感谢 Bergi 和 @MarcDavies!不过我有一个问题,因为我想保留索引模式,为什么将 Checkbox 移动到这样的迷你模块可以解决问题?我不太清楚为什么我的 DateSelect 组件在具有这些循环依赖项的情况下工作,哈哈。

标签: javascript reactjs webpack es6-modules


【解决方案1】:

您的问题是您对/components/index.js 中的导出存在循环依赖。

当您的构建工具首次构建您的应用程序时,它会执行以下操作...

  • app.js 开始,从components/index.js 导入DateSelect
  • 转到声明其导出的components/index.js

    // SPOILER: This guy is your problem. We'll call him the douche.
    export {
      DateSelect,
      Button,
      Checkbox,
    };
    
  • 要声明冲洗器,它需要从components/DateSelect.jscomponents/Button.jscomponents/Checkbox.js 导入内容

  • 转到这些文件并开始构建其中的内容
  • 一旦构建DateSelect,就会被告知从components/index.js导入CheckBox
  • 它已经知道components/index.js 中的冲洗器,所以它会在冲洗器上寻找CheckBox(它“循环”回到它),但CheckBox 尚未在components/CheckBox.js 中声明,所以它返回undefined

这是时间问题。 CheckBox 将在我们尝试导入后单击几下在冲洗器上定义,但这对您的构建来说太晚了。

您可以通过在 import 语句之后立即记录 CheckBox 并在几毫秒后记录这一点...

// /components/DateSelect/DateSelect.js:

import { Checkbox } from '../';
console.log(CheckBox); // undefined
setTimeout(() => console.log(CheckBox), 4) // function CheckBox(_ref) { ...

因此,当您尝试声明 MyCustomCheckbox 时,CheckBox 未定义。

DateSelect 的渲染方法被调用时,CheckBox 已经被声明了。相对而言,这比构建过程晚。

此类问题没有 100% 安全的解决方案,因为它取决于您的应用程序整体结构。通常最好避免循环回到同一个模块

避免冲洗

您可以通过直接从 components/CheckBox.js 导入来避免循环往复,或者将索引模块更深一层并创建一个小 CheckBox 模块...

/components
  /CheckBox
    CheckBox.js
    index.js

...然后import {CheckBox} from '/components/Checkbox'

【讨论】:

  • 冲洗器不是一个对象。虽然它看起来类似于对象文字,但它只是导出名称的声明。与您的建议 (export {DateSelect} from './DateSelect';) 的唯一区别在于它等同于 export {default as DateSelect} from './DateSelect';,而不是 export {DateSelect as DateSelect} from './DateSelect';
  • OK @Bergi 所以我误解了引擎盖下冲洗的性质。将编辑出来。但确实,使用冲洗方法会创建一个导出实体,该实体具有构建工具循环返回的稳定标识。所以 - 使用单独的导出语句的方法有效。我也不确定你为什么批评命名的导出方法 - 它显然更干净。
  • @Bergi 否决直接回应问题并提供有效解决方案但会产生小技术错误的答案是否合适/有帮助?无论如何 - 模块加载的实现并不是完全透明的,所以我不认为将导出作为对象来进行这种解释是太大的犯罪。
  • 我不知道你的误解有多大,有些事情是正确的,有些事情(次要的?)没有,但是你得到了一个错误的解决方案。感谢您修复术语对象,下一个错误将是“CheckBox 尚未声明” - 实际上它已被声明(这就是它在那里被发现的原因),但还没有初始化。我主要对第一个“解决方案”投了反对票,但它并不能真正起作用——改变重新导出语法并没有什么不同。它仍然回到index.js 模块,你改变的只是默认导出与命名导出。
  • ...当我让它工作时,我碰巧有语句以一种方式排序,这意味着CheckBox 及时被完全声明(声明+实例化+分配)。当您切换顺序时,这不会及时发生。无论如何,这不是一个稳定的解决方案,所以我会编辑出来。
猜你喜欢
  • 2016-07-21
  • 1970-01-01
  • 1970-01-01
  • 2022-01-21
  • 2021-08-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-12-28
相关资源
最近更新 更多