通过 .map 循环创建按钮时出现问题

Problem creating buttons through .map loop

我正在尝试在 JavaScript 中创建一个基本清单。我想将项目添加到一个数组中,该数组将是玩家库存。库存数组中的内容将显示在多个 div 上(每个项目一个)。我已经开始体验并让基础知识发挥作用,但我想详细说明并为项目列表中的每个项目创建按钮,以便稍后使用按钮将项目添加到库存中。

我首先选择 HTML div 并创建一个基本的 itemList 数组和一个 playerInv 空数组:

const container = document.querySelector('.container');
const inventory = document.querySelector('.inventory');
const invItem = document.querySelectorAll('.grid-item');

const itemList = [{
    id: 1,
    name: "Coke Bottle",
    quality: "usable"
  },
  {
    id: 6,
    name: "Pair of Jeans",
    quality: "pants"
  }
]

// Player inventory, is by default empty.
const playerInv = []

const itemBtnContainer = document.createElement("div");
itemBtnContainer.classList.add("item-add-btn-ctn");
container.appendChild(itemBtnContainer);

let itemBtn;

function addItemBtn() {
  itemList.map((item) => {
    itemBtn = document.createElement("button");
    itemBtn.classList.add("item-add-btn");
    itemBtnContainer.appendChild(itemBtn);
    itemBtn.innerText += item.name;
  })
}
addItemBtn();
<div class="container">
  CONT
  <div class="inventory">
    <div class="inventory-left">
      <div class="inventory-left-title">Your inventory</div>
      <div class="inventory-left-grid">
        <div class="grid-item"></div>
        <div class="grid-item"></div>
        <div class="grid-item"></div>
        <div class="grid-item"></div>
        <div class="grid-item"></div>
        <div class="grid-item"></div>
        <div class="grid-item"></div>
        <div class="grid-item"></div>
        <div class="grid-item"></div>
        <div class="grid-item"></div>
      </div>
    </div>
    <div class="inventory-right"></div>
  </div>
</div>

因此,为了创建我的按钮,我的想法是使用地图循环遍历我的 itemList 数组。每个 itemList 项目都会创建一个带有 class 的“item-add-btn”按钮,并且在按钮内会显示项目的名称。

从视觉上看,它有效。由于 6 个 itemList 项,我有 6 个按钮,但是因为我在循环中创建了按钮,所以我无法在循环外访问它们。实际上,如果我 console.log(itemBtn) 它 returns 'undefined',那么我无法使用 addEventListener 创建 onClick 函数以便稍后编写我的 addItemToInv() 函数。 我明白为什么它不起作用,但我真的不知道我还能做些什么来获得相同的结果并能够使用我的按钮。

我什至不知道我还能尝试什么,因为这是一个非常特殊的需求。

不清楚为什么您需要访问循环外的按钮。您显然正在使用地图,所以 return 按钮和您现在有一个按钮数组。

let itemBtns;

function addItemBtn() {
    itemBtns = itemList.map((item) => {
        itemBtn = document.createElement("button");
        itemBtn.classList.add("item-add-btn");
        itemBtnContainer.appendChild(itemBtn);
        itemBtn.innerText = item.name;
        return itemBtn;
    });
}

但您可能只想知道按钮何时被单击,因此您应该在创建按钮时将事件侦听器添加到按钮。


function addItemBtn() {
    itemList.forEach((item) => {
        itemBtn = document.createElement("button");
        itemBtn.classList.add("item-add-btn");
        itemBtnContainer.appendChild(itemBtn);
        itemBtn.innerText = item.name;
        itemBtn.type = "button";
        itemBtn.addEventListener("click", () => { 
          console.log(item); 
        });
    });
}

首先,您应该使用 for .. ofArray.prototype.forEach,因为 .map 构建了一个您正在丢弃的新数组。

其次,创建一个 createButton 函数来 returns 按钮,这样你就可以将它存储到一个值 -

function createButton(item) {
  const itemBtn = document.createElement("button")
  itemBtn.type = "button"
  itemBtn.classList.add("item-add-btn")
  itemBtn.innerText += item.name
  return itemBtn
}

现在你可以循环了 -

for (const item of itemList) {
  // create button reference
  const button = createButton(item)

  // add click handler
  button.addEventListener("click", ...)

  // append button to container
  itemBtnContainer.appendChild(button)
}

这是一个完整的工作演示 -

const inventory = []

const itemList = [{id:1,name: "Coke Bottle",quality: "usable"},{id: 6,name: "Pair of Jeans",quality: "pants"}]

function renderInventory() {
  document
  .querySelector("#inventory")
  .textContent = `inventory: ${JSON.stringify(inventory, null, 2)}`
}

function createButton(text, onClick) {
  const e = document.createElement("button")
  e.type = "button"
  e.textContent = text
  e.addEventListener("click", onClick)
  return e
}

function addToInv(item) {
  return event => {
    inventory.push(item)
    renderInventory()
  }
}

renderInventory()

for (const item of itemList)
  document.body.appendChild(createButton(item.name, addToInv(item)))
#inventory {
  padding: 1rem;
  background-color: #eee;
  font-family: monospace;
}
<pre id="inventory"></pre>

在您的地图中,您永远不会 return 您创建的 itemBtn。如果您像下面这样稍微修改一下代码,您应该会得到一个可以使用的按钮元素列表。

function addItemBtn() {
    return itemList.map((item) => {
        const itemBtn = document.createElement("button");
        itemBtn.classList.add("item-add-btn");
        itemBtnContainer.appendChild(itemBtn);
        itemBtn.innerText += item.name;
        return itemBtn;
    });
}

const buttons = addItemBtn(); // List of button elements to do with what you will

我确实想提一下,一旦您定义了 addItemToInv 函数,您就可以执行如下操作:

function addItemToInv(item) {
  // Logic to add item to inventory here
}

function addItemBtn() {
    return itemList.map((item) => {
        const itemBtn = document.createElement("button");
        itemBtn.classList.add("item-add-btn");
        itemBtnContainer.appendChild(itemBtn);
        itemBtn.innerText += item.name;
        itemBtn.addEventListener("click", () => addItemToInv(item));
        return itemBtn;
    });
}

只需将您的监听器添加到您的循环中否?

itemBtn.addEventListener('click', function(event){
  const elem = event.target;
});

为什么全局 itemBtn ?