【问题标题】:ES6: share data between modulesES6:模块间共享数据
【发布时间】:2016-03-17 12:36:16
【问题描述】:

为了在模块之间共享数据,通常的模式是将数据封装到一个公共模块中,然后将其导入其他模块中。

在我的例子中,要共享的数据是一个记录器,需要在使用前进行初始化。我在应用程序的入口点调用init()

// main.js

let logger = require('@my/logger');
logger.init({...});

let xxx = require('./moduleX');
let yyy = require('./moduleY');

在其他模块中,可以使用初始化的logger:

// moduleX.js

let logger = require('@my/logger');
const log = logger('moduleX');

function x() {
  log.debug('some msg');
}

以上代码在 node.js 中运行良好。但是如果我更改为 ES6 模块语法,它就不起作用了,因为 ES6 模块导入被提升了。

// main.js

import {logger} from '@my/logger';
logger.init({...});          // this line is run after import moduleX
import {x} from './moduleX';

// moduleX.js

import {logger} from '@my/logger';
const log = logger('moduleX');        // logger is not initialized !

export function x() {
  log.debug('some msg');
}

使用 ES6 模块,我如何初始化一些数据并将它们共享给其他模块?

有一个类似的question,但答案不适合我的情况。

更新:

一些答案​​建议将访问共享数据的代码放入函数中,以便在模块加载时不会立即调用代码。但是如果我真的需要在模块加载期间访问它怎么办?我更新了我的代码来演示用例——如果不将 log 作为模块范围 const,在每个函数中调用 logger(name) 就太简单了。

【问题讨论】:

  • 为什么不直接创建另一个文件initializeLogger.js 并在其中调用logger.init 并将该文件导入main.js
  • @PaoloMoretti 我想过这种方式,但它需要另一个文件,对我来说看起来不够简洁。但是,这似乎是解决它的唯一方法。你能写一个答案让我接受吗?
  • 在调用logger(…) 之前记录器需要进行什么初始化?不是只有log.debug(…)需要等待吗?
  • @Bergi 输出目标、应用程序名称等。您可能会说对于记录器没有必要这样做。我要求一种通用模式来处理这种需求:初始化一个对象并将其共享给每个模块。
  • @aleung:嗯,您链接的“类似问题”似乎已经回答了这个问题。但我在下面写了一个明确的答案。

标签: javascript module ecmascript-6


【解决方案1】:

最后我用@PaoloMoretti 在他的评论中提到的方式解决了这个问题。

在我的应用中编写一个模块来为我的应用初始化记录器:

// logger_init.js
import {logger} from '@my/logger';
logger.init({...});

在应用程序的入口点导入初始化模块一次,然后再导入任何其他使用记录器的模块。它保证在加载其他模块之前完成初始化。

// main.js
import './logger_init'; 
import {x} from '@my/other_module';
import {y} from './module_in_myapp';

其他模块可以直接使用初始化的logger:

// @my/other_module

import {logger} from '@my/logger';
const log = logger('moduleX');       // logger has been initialized

export function x() {
  log.debug('some msg');
}

依赖树是:

                           <init>
myapp --+--> logger_init ------------> @my/logger
        |                       <use>   ↑ 
        +--> module_in_myapp -----------+
        |                       <use>   |
        +--> @my/other_module ----------+

为什么我不采用添加初始化并返回记录器的包装器模块的方式(如 Bergi 的回答),因为使用记录器的模块可能是不在我的应用程序中的可重用模块。

【讨论】:

  • 你甚至可以只做import './logger_init';的副作用
【解决方案2】:

尝试为您的 xxx.js 和 yyy.js 模块提供一些入口点,甚至将它们作为函数。

// main.js
import {logger} from '@my/logger';
import * as xxx from './xxx';

logger.init({...});

xxx.run();

// xxx.js
import {logger} from '@my/logger';
export function run () {
   logger.debug('In xxx');
}

【讨论】:

    【解决方案3】:

    您可以让每个依赖于某些初始化例程的模块返回一个可以手动执行以调用其功能的函数,而不是将该功能暴露为导入它的直接副作用。比如在xxx.js:

    // xxx.js
    export default function (log) {
        log('xxx');
    }
    

    然后将所有初始化操作放在一个入口文件中,并在这些操作完成后调用上述方式定义的模块

    // main.js
    import xxx from './xxx';
    import {logger} from '@my/logger';
    logger.init({...});
    xxx(logger);
    

    但是如果我真的需要在模块加载期间访问它呢?

    请参阅上面修改后的代码示例。将logger的实例传递给各个模块导出的函数。

    【讨论】:

    • 谢谢,但如果我真的需要在模块加载期间访问它怎么办?请阅读我的问题更新。
    • 为什么它需要在导入时立即运行?在我的回答中使用延迟执行并将记录器的实例传递给每个模块函数? (我已经更新了我的答案。)
    • 我已经更新了我的代码。在模块中,我需要调用log = logger('moduleX'); 将记录器名称设置为模块名称。如果不将模块范围设为 const,那么在每个函数中设置记录器名称就太简单了。顺便说一句,在您修改后的代码中,将记录器作为函数参数传递是另一个好处。我个人喜欢将记录器排除在参数列表之外。
    • 要么您必须修改 logger 的工作方式,以便在初始化之前可以像 logger('moduleX') 一样调用它(然后您的问题中的示例将起作用),或者您将不得不使用我在回答中概述的方法。
    【解决方案4】:

    一个记录器,在使用前需要初始化。如果我需要在模块加载期间访问它怎么办?

    使用初始化的记录器制作一个额外的模块:

    // main.js
    import {x as xxx} from './moduleX';
    import {y as yyy} from './moduleY';
    

    // logger_ready.js
    import {logger} from '@my/logger';
    logger.init({…});
    export default logger;
    

    // moduleX.js
    import logger from 'logger_ready';
    const log = logger('moduleX');        // logger is initialized here!
    
    export function x() {
      log.debug('some msg');
    }
    

    【讨论】:

      【解决方案5】:

      经过实验,发现BroadcastChannel api非常适合在不同的es6模块、标签、worker、frame之间轻松共享数据或事件...

      我们也可以使用 json stringify 来传递对象或数组:

      发送数据:

      new BroadcastChannel('myapp_section8').postMessage('Hello world!')
      

      示例接收,来自另一个模块:

      new BroadcastChannel('myapp_section8').addEventListener('message', function(e){
        console.log(e.data) 
      })
      

      dispatchEvent 避免使用任何 DOM 元素,它简化了很多

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2017-10-04
        • 1970-01-01
        • 2015-10-08
        • 1970-01-01
        • 2017-11-24
        • 1970-01-01
        • 2022-01-17
        • 2019-12-21
        相关资源
        最近更新 更多