动态 HTML 上的事件绑定,使用 fetch 和 forEach 内

Posted

技术标签:

【中文标题】动态 HTML 上的事件绑定,使用 fetch 和 forEach 内【英文标题】:Event binding on dynamic HTML, using fetch and within forEach 【发布时间】:2021-01-17 08:41:05 【问题描述】:

我正在尝试在下面显示的 html 元素上添加一个事件侦听器,在 forEach 循环中动态生成。我最初从 JSON 占位符 API 获得了正确的响应,但是,当我尝试事件时,在初始响应之后生成的任何 HTML 元素上绑定事件的尝试似乎都没有产生结果。

我要么根本没有“点击”,要么...addEventListener 不是一个函数,这取决于我将addEventListener 代码放在哪里。

有没有办法以一种干净且可重复使用的方式实现这一点? 另外,这是通过 vanilla JS 生成动态 HTML 的首选方式吗?还是我应该手动生成它,例如使用document.createElement()appendChild()

最终,我的意思是将点击目标的 ID 传递给 getPosts 函数获取 URL。

任何帮助将不胜感激!

fetch('https://jsonplaceholder.typicode.com/users')
  .then(res => res.json())
  .then(data => createResult(data));

function createResult(data) 
  const container = document.getElementById('result');

  data.forEach((user) => 
    const  id, name, email, address:  city, street   = user;

    let result =
      `<div class="user" data-uid=$id>
          <h5 id="user-$id"> User ID: $id </h5>
            <ul class="w3-ul">
              <li> User Full Name : $name</li>
              <li> User Email : $email </li>
              <li> User Address : $city, $street </li>
            </ul>
        </div>`;

    container.innerHTML += result;
    
    document.body.addEventListener("click", function (e) 
      if (e.target &&
        e.target.classList.contains("user")) 
        console.log('Clicked');
      
    );
  );


function getPosts(e) 
  fetch(`https://jsonplaceholder.typicode.com/users/$user.id`)
    .then(res => res.json())
    .then(data => console.log(data));
&lt;div id=result&gt;&lt;/div&gt;

【问题讨论】:

假设你有一个 result 元素,它看起来可以工作,但你应该只附加一次监听器,而不是每次 为什么要为document.body data 中的每个元素添加一个新的click 处理程序?将click 处理程序添加到#result @Andreas 这是我在网上遇到事件委托后尝试的方法。老实说,我不知道这是要走的路。通常我只会在元素本身上添加一个事件侦听器,但在这种情况下似乎不起作用。 @CertainPerformance 你指的点击事件?我已成功收到来自jsonplaceholder.typicode.com/users 的初始回复。点击事件根本不会触发。 因为在.innerHTML += ... 部分之后只有一个“元素”(它会杀死所有已经附加的事件处理程序!)。然后,您必须将事件附加到 #result 的最后一个孩子。坚持使用事件委托,但只向#result 添加一个处理程序并使用.insertAdjacentHTML() 而不是.innerHTML 【参考方案1】:

您应该使每个结果都成为一个元素,并仅向该元素添加事件侦听器

let element = document.createElement('div')
element.innerHTML = result
element = element.firstChild

element.addEventListener("click", function (e) 
  console.log('Clicked', element.getAttribute('data-uid'));
);

container.appendChild(element);

可运行示例

fetch('https://jsonplaceholder.typicode.com/users')
  .then(res => res.json())
  .then(data => createResult(data));

function createResult(data) 
  const container = document.getElementById('result');

  data.forEach((user) => 
    const  id, name, email, address:  city, street   = user;

    let result =
      `<div class="user" data-uid=$id>
          <h5 id="user-$id"> User ID: $id </h5>
            <ul class="w3-ul">
              <li> User Full Name : $name</li>
              <li> User Email : $email </li>
              <li> User Address : $city, $street </li>
            </ul>
        </div>`;

    let element = document.createElement('div')
    element.innerHTML = result
    element = element.firstChild
    
    element.addEventListener("click", function (e) 
      console.log('Clicked', element.getAttribute('data-uid'));
    );
    
    container.appendChild(element);
  );


function getPosts(e) 
  fetch(`https://jsonplaceholder.typicode.com/users/$user.id`)
    .then(res => res.json())
    .then(data => console.log(data));
&lt;div id=result&gt;&lt;/div&gt;

【讨论】:

你知道,在专业环境中,更常见的是像我在帖子中那样生成 HTML(如字符串)还是通过 createElement 等生成?我正在尝试使用 fetch,所以我想知道哪种方法可以让我走上正确的道路。【参考方案2】:

应该可以工作

    您将 eventListener ONCE 附加到 document.body 您向 addEventListener 调用添加第三个参数,指定使用 CAPTURE 而不是 BUBBLING。

function createResult(data) 
  const container = document.getElementById('result');

  data.forEach((user) => 
    const  id, name, email, address:  city, street   = user;

    let result =
      `<div class="user" data-uid=$id>
          <h5 id="user-$id"> User ID: $id </h5>
            <ul class="w3-ul">
              <li> User Full Name : $name</li>
              <li> User Email : $email </li>
              <li> User Address : $city, $street </li>
            </ul>
        </div>`;

    container.innerHTML += result;
  );


document.body.addEventListener("click", function (e) 
  if (e.target &&
    e.target.classList.contains("user")) 
    console.log('Clicked element ' + e.target.data.uid);
  
, false);

【讨论】:

啊,是的,这很重要!您将如何将点击元素的 ID 传递给 getPosts 函数中的 fetch 参数? e.target.data.uid 这是.dataset 不是.data【参考方案3】:

您的方法存在两个问题。第一个问题是您为data 中的每个元素添加click 处理程序到document.body

如果您只调用一次createResult(),则将.addEventListener() 调用移动到.forEach() 之前或之后

container.addEventListener("click", ...);
data.forEach(...);

如果您多次调用createResult(),则将.addEventListener() 移出它并可能在DOMContentLoaded 上调用它

window.addEventListener("DOMContentLoaded", () => 
  document.getElementById('result').addEventListener("click", ...);
);

第二个问题是e.targete.target 是触发 click 事件的元素。这很可能不是div.result,而是div.result 中的一个元素。您必须遍历 DOM,直到找到 div.result。一种可能的方法是使用Element.closest() 或循环和Node.parentElement

fakeFetch().then(createResult);

function createResult(data) 
  const container = document.getElementById('result');

  container.addEventListener("click", function (e) 
    const user = e.target.closest(".user");
    
    if (user) 
      getPosts(user.dataset.uid);
    
  );

  data.forEach((user) => 
    const  id, name, email, address:  city, street   = user;

    let result =
      `<div class="user" data-uid="$id">
          <h5 id="user-$id"> User ID: $id </h5>
            <ul class="w3-ul">
              <li> User Full Name : $name</li>
              <li> User Email : $email </li>
              <li> User Address : $city, $street </li>
            </ul>
        </div>`;

    container.innerHTML += result;
  );


function getPosts(userId) 
  console.log(userId);


function fakeFetch() 
  return new Promise(resolve => 
    const user = [
       id: 1, name: "a", mail: "a@a.a", address:  city: "a", street: "a"  ,
       id: 2, name: "b", mail: "b@b.b", address:  city: "b", street: "b"  
    ];
    
    setTimeout(resolve(user), 1000);
  )
&lt;div id="result"&gt;&lt;/div&gt;

第二点: 不要使用.innerHTML。这将覆盖元素的内容并因此删除任何事件处理程序。请改用Element.insertAdjacentHTML()

container.innerHTML += result

然后是

container.insertAdjacentHTML("beforeend", result);

【讨论】:

我会像你说的那样阅读 Element.insertAdjacentHTML() 。这种方法很有意义。你能告诉我,我是否会更好地使用 createElement 等而不是我现在正在做的方式生成 HTML?询问未来的实践目的,因为我没有看到很多人创建这样的 HTML,它可能会使事情变得不必要的复杂。 如果你纯粹使用 javascript 构建 DOM(或在更大范围内),你肯定想看看一个库(react/vue/...)。 DOM 方法(.createElement().appendChild(),...)几乎可以保证您不会对 DOM 或对 DOM 做任何坏事。如果您绝对确定字符串只包含有效的 HTML,请使用 .insertAdjacentHTML() 而不是旧的 .innerHTML 如果您使用事件委托,请始终将事件处理程序添加到最近的可能父节点。如果您只对点击#result 感兴趣,请使用#result。将其添加到,例如body,将导致您的事件处理程序为页面上的每次点击执行,您将必须添加额外的检查何时发生在#result内的点击 我一定会研究这些库。我一直想构建一个日历 web 应用程序,允许用户注册、查看他们的日/周/月议程、更改他们的议程等。这会产生很多动态 HTML,所以使用 React 或 Vue 可能会有所帮助就像你提到的。感谢所有的好信息!

以上是关于动态 HTML 上的事件绑定,使用 fetch 和 forEach 内的主要内容,如果未能解决你的问题,请参考以下文章

动态创建元素的事件绑定?

动态创建元素的事件绑定?

动态创建元素的事件绑定?

动态创建元素的事件绑定?

动态创建元素的事件绑定?

动态创建元素的事件绑定?