动态 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));
<div id=result></div>
【问题讨论】:
假设你有一个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));
<div id=result></div>
【讨论】:
你知道,在专业环境中,更常见的是像我在帖子中那样生成 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.target
。 e.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);
)
<div id="result"></div>
第二点:
不要使用.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 内的主要内容,如果未能解决你的问题,请参考以下文章