【问题标题】:Use parameterised functions defined in ES6 module directly in html直接在 html 中使用 ES6 模块中定义的参数化函数
【发布时间】:2021-12-11 16:19:30
【问题描述】:

嵌入在 HTML 脚本中的 ES6 模块中定义的函数对该脚本不可用。因此,如果您有如下声明:

<button onclick="doSomething();">Do something</button>

在您的 HTML 中,并且您的 doSomething() 函数位于嵌入在 HTML 脚本中的 ES6 模块中,当您运行该脚本时,您将收到“doSomething() is undefined”错误。

Use functions defined in ES6 module directly in html 为当前问题提出了一个很好的解决方案,建议您通过修改 HTML 将您的功能“绑定”到按钮:

<button id="dosomethingbutton">Do something</button>

并使用模块本身来创建链接:

document.getElementById('dosomethingbutton').addEventListener('click', doSomething);

这很好用,但是如果您的原始按钮更复杂一些并且被参数化了怎么办?例如:

<button onclick="doSomething('withThisString');">Do Something with String</button>

“绑定”所能提供的最大功能似乎仅限于与事件相关的情况——我找不到将它与数据相关联的方法。我完全被困在试图找到解决方案的过程中,我们将不胜感激。

我想补充一点,虽然这个问题可能看起来有点晦涩难懂,但我认为任何迁移到 Firebase 9 的人都会对此感兴趣。在其他更改中,迁移要求您将 javascript 代码移动到 ES6 模块中(其中命名空间不能直接用于 HTML DOM),因此最简单的 HTML 很可能会立即遇到这些问题。欢迎提供建议。

【问题讨论】:

    标签: javascript html firebase ecmascript-6


    【解决方案1】:

    虽然 T.J Crowder 已经回答了这个问题,但我想我可能会添加一些作为 cmets 很难挤进去的点。

    当我深入了解 Firebase V9 转换后,我开始发现“模块命名空间”问题的一些后果非常严重。如上所述,我最初的问题中引用的示例很容易处理,但我发现我还需要弄清楚如何处理“动态”HTML 以响应从数据库派生的可变情况。在这种情况下,我的 javascript 最初会创建一个包含 HTML 块的字符串,例如:

    realDiv = `
    <div>
        <button onclick = "function fn1 (param1a, param1b,...);">button1</button>
        <button onclick = "function fn2 (param2a, param2b,...);">button2</button>
    etc
    </div>
    `
    

    然后将其放入应用程序的 HTML 框架中定义的“真实”realdiv 中,并带有一个

    document.getElementById("realdiv") = realDiv;

    现在,由于上述原因,一旦 javascript 在一个模块中,这种安排就不再起作用了。

    我学会采用的模式(再次感谢 T.J Crowder)遵循以下思路:

    realDiv = `
    <div>
        <button id = "button1" data-param1a="param1a" data-param1b="param1b";">button1</button>
        <button id = "button2" data-param2a="param2a" data-param2b="param2b";">button2</button>
    etc
    </div>
    `
    

    然后我会像以前一样将生成的代码放入我的 HTML 框架中

    document.getElementById("realdiv") = readlDiv;

    现在您已经将代码嵌入到 DOM 中(并假设我已经计算了您生成的按钮的数量),我将使用最后一点 javascript 为它们创建绑定,就像这样:

    for (let i = 0; i>buttonCount; i++) {
        document.getElementById('button' + i).onclick = function() { fn1 (this.getAttribute('data-param1a'), this.getAttribute('data-param1b') };
    etc
    }
    

    我发现,当我需要让 onclick 启动许多不同的功能时,使用这种模式创建 onclick 在保持清晰度方面特别有帮助。

    【讨论】:

      【解决方案2】:

      这很好用,但如果您的原始按钮更复杂一些并已参数化怎么办?

      有几个解决方案:

      1. data-* 属性:

        <button id="the-button" data-string="withThisString">Do Something with String</button>
        
        document.getElementById("the-button").addEventListener("click", function() {
            doSomething(this.getAttribute("data-string"));
        });
        

        (更多内容见下文。)

      2. 绑定事件时绑定字符串

        <button id="the-button">Do Something with String</button>
        
        document.getElementById("the-button").addEventListener("click", () => {
            doSomething("withThisString");
        });
        

      上面有很多变体,如果您将doSomething 与具有不同字符串的多个按钮一起使用,您可以使用类和循环而不是id 来执行#1,但这是一般的想法。


      关于data-* 属性的事情:如果您愿意,您可以通过data-* 属性和一个连接事物的单个函数使这个过程完全由HTML 驱动。例如,假设您有这些按钮:

      <button data-click="doThisx@module1">Do This</button>
      <button data-click="doThat@module2">Do That</button>
      <button data-click="doTheOther@module3">Do The Other</button>
      

      你可以有一个可重用的函数来连接它们:

      class EventSetupError extends Error {
          constructor(element, msg) {
              if (typeof element === "string") {
                  [element, msg] = [msg, element];
              }
              super(msg);
              this.element = element;
          }
      }
      export async function setupModuleEventHandlers(eventName) {
          try {
              const attrName = `data-${eventName}`;
              const elements = [...document.querySelectorAll(`[${attrName}]`)];
              await Promise.all(elements.map(async element => {
                  const attrValue = element.getAttribute(`data-${eventName}`);
                  const [fname, modname] = attrValue ? attrValue.split("@", 2) : [];
                  if (!fname || !modname) {
                      throw new EventSetupError(
                          element,
                          `Invalid '${attrName}' attribute "${attrValue}"`
                      );
                  }
                  // It's fine if we do import() more than once for the same module,
                  // the module loader will return the same module
                  const module = await import(`./${modname}.js`);
                  const fn = module[fname];
                  if (typeof fn !== "function") {
                      throw new EventSetupError(
                          element,
                          `Invalid '${attrName}': no '${fname}' on module '${modname}' or it isn't a function`
                      );
                  }
                  element.addEventListener(eventName, fn);
              }));
          } catch (error) {
              console.error(error.message, error.element);
          }
      }
      

      使用它来查找和连接点击处理程序:

      import { setupModuleEventHandlers } from "./data-attr-event-setup.js";
      setupModuleEventHandlers("click")
      .catch(error => {
          console.error(error.message, error.element);
      });
      

      它是一次性管道,但在 HTML 中为您提供相同的基于属性的体验(事件处理程序仍然可以从另一个 data-* 属性获取参数信息,或者您可以将其烘焙到您的设置函数中)。 (该示例依赖于dynamic import,但那是supported by recent versions of all major browsers,并且在不同程度上依赖于捆绑器。

      有几十种方法可以旋转它,我不是在宣传它,只是举例说明你可以根据需要轻松做的事情。

      但实际上,这正是 React、Vue、Ember、Angular、Lit 等库发挥作用的地方。

      【讨论】:

      • 非常感谢先生。我应用了建议(2),它很有效。作为后续,您认为 ES6 标准中缺少什么吗?我转换后的 Firebase 8 代码看起来一团糟。在引入 ES6 模块之前,在我的 HTML 中定义的清除按钮链接现在已经消失,现在困扰我的 Javascript 顶部的“绑定”语句块并不是一个令人满意的替代品。
      • @MartinJ - 这不是 JavaScript 的东西,而是 DOM/HTML 的东西。问题是onxyz-attribute-style 事件处理程序只能调用全局函数。 (如果你真的想,你可以在模块中定义全局变量,但这有点违背模块的目的。你可以通过分配给globalThiswindow [在浏览器上] 上的属性来做到这一点。)它看起来确实像DOM/HTML 可以在这方面使用一些东西,我知道围绕它有一些试探性的讨论,但是在标准中定义它非常很棘手,因为存在很多相互冲突的意见关于如何去做。这个差距一般是……
      • ...由库和/或框架填充。但是你可以自己填写;我已经用模块友好的属性驱动事件处理示例更新了答案。
      • @TJC 谢谢。我对 React 有足​​够的了解,可以想象它的 virtualDOM 可能会为我的问题提供解决方案。但我的问题是,我需要很长时间才能适应使用它的库。
      猜你喜欢
      • 2019-05-06
      • 2015-09-01
      • 2019-01-10
      • 2017-01-15
      • 2017-11-19
      • 2020-04-09
      • 2018-02-26
      • 2020-07-04
      相关资源
      最近更新 更多