嵌套的网络组件和事件处理

Nested web-components and event handling

我正在 javascript 中编写记忆游戏。我为纸牌制作了一个 Web 组件 <memory-card> 和一个包含纸牌并处理游戏状态的 Web 组件 <memory-game><memory-card> class 包含翻转时的图像路径、显示为卡片背面的默认图像、翻转状态和处理状态切换的 onclick 函数和图片。

<memory-game> class 有一个 setter 接收图像数组以从中生成 <memory-cards>。在 <memory-game> class 中处理更新游戏状态的最佳方法是什么?我应该将额外的事件侦听器附加到那里的 <memory-card> 元素还是有更好的方法来解决它?我希望 <memory-card> 元素像现在一样只处理它们自己的功能,即根据单击时的状态更改图像。

内存-game.js

class memoryGame extends HTMLElement {
  constructor () {
    super()
    this.root = this.attachShadow({ mode: 'open' })
    this.cards = []
    this.turnedCards = 0
  }

  flipCard () {
    if (this.turnedCards < 2) {
      this.turnedCards++
    } else {
      this.turnedCards = 0
      this.cards.forEach(card => {
        card.flipCard(true)
      })
    }
  }

  set images (paths) {
    paths.forEach(path => {
      const card = document.createElement('memory-card')
      card.image = path
      this.cards.push(card)
    })
  }

  connectedCallback () {
    this.cards.forEach(card => {
      this.root.append(card)
    })
  }
}

customElements.define('memory-game', memoryGame)

内存-card.js

class memoryCard extends HTMLElement {
  constructor () {
    super()
    this.root = this.attachShadow({ mode: 'open' })
    // set default states
    this.turned = false
    this.path = 'image/0.png'
    this.root.innerHTML = `<img src="${this.path}"/>`
    this.img = this.root.querySelector('img')
  }

  set image (path) {
    this.path = path
  }

  flipCard (turnToBack = false) {
    if (this.turned || turnToBack) {
      this.turned = false
      this.img.setAttribute('src', 'image/0.png')
    } else {
      this.turned = true
      this.img.setAttribute('src', this.path)
    }    
  }

  connectedCallback () {
    this.addEventListener('click', this.flipCard())
  }
}

customElements.define('memory-card', memoryCard)

在 Supersharp 的回答后执行自定义事件

内存-card.js(提取)

connectedCallback () {
    this.addEventListener('click', (e) => {
      this.flipCard()
      const event = new CustomEvent('flippedCard')
      this.dispatchEvent(event)
    })
  }

内存-game.js(提取)

  set images (paths) {
    paths.forEach(path => {
      const card = document.createElement('memory-card')
      card.addEventListener('flippedCard', this.flipCard.bind(this))
      card.image = path
      this.cards.push(card)
    })
  }

<memory-card>中:

  • 使用 CustomEvent() 创建并使用 dispatchEvent()
  • 发送自定义事件

<memory-game>中:

  • 使用 addEventListener()
  • 收听您的自定义事件

因为卡片嵌套在游戏中,事件会自然地冒泡到容器中。

这样 2 个自定义元素将保持松散耦合。

查看您现有的一些代码以了解您尝试过的内容将非常 很有帮助。但是如果没有它,你就不能按照@Supersharp 的建议去做,或者你可以让 <memory-game> class 处理所有事件。

如果你这样做,那么你的 <memory-card> 代码将监听整个字段上的 click 事件。它会检查你是否点击了一张仍然面朝下的卡片,如果是,告诉卡片翻转。 (通过设置 属性 或属性,或通过调用 <memory-card> 元素上的函数。)

所有其他逻辑都将存在于 <memory-game> class 中,以确定所选择的两张牌是否相同并分配分数等

如果您希望卡片处理 click 事件,那么您可以让该代码生成一个新的 CustomEvent 以指示卡片已翻转。可能包括卡在网格内的坐标和正在翻转的卡的类型。

<memory-game> class 然后会监听 flipped 事件并根据该信息采取行动。

不管你怎么做,这都不是真正的问题。这只是你想要如何编码它以及你想要代码如何捆绑在一起。如果您从未打算在任何其他游戏中使用此代码,那么它并不重要。

Supersharp 答案并非 100% 正确。

click 事件冒泡 DOM, 但 CustomEvents(在 shadowDOM 内)

Why firing a defined event with dispatchEvent doesn't obey the bubbling behavior of events?

所以你必须自己添加 bubbles:true:

[yoursender].dispatchEvent(new CustomEvent([youreventName], {
                                                  bubbles: true,
                                                  detail: [yourdata]
                                                }));

更多:https://javascript.info/dispatch-events

注意:detail可以是一个函数:

基于事件的编程挑战

   this.cards.forEach(card => {
    card.flipCard(true)
  })

首先this.cards不是必需的,因为所有卡牌在[...this.children]

中都可用

!!请记住,在 JavaScript Objects 中通过引用传递,因此您的 this.cards 指向确切的 same DOM children

你在这里有依赖关系,
游戏需要知道 Card.

中的 .flipCard 方法

► 让您的记忆游戏发送一个事件,每张卡都会收到该事件

提示:每张卡片都需要 'listen' 在游戏 DOM 级别才能接收冒泡事件

在我的代码中,整个循环是:

game.emit('allCards','close');

卡片负责监听正确的 EventListener
(附于card.parentNode

这样一来,您的游戏中有多少(或什么)牌都没有关系

DOM就是你的data-structure

如果您的游戏不再关心有多少或什么 DOM children,
而且它不对已有的元素做任何簿记,
洗牌变得小菜一碟:

  shuffle() {
    console.log('► Shuffle DOM children');
    let game = this,
        cards = [...game.children],//create Array from a NodeList
        idx = cards.length;
    while (idx--) game.insertBefore(rand(cards), rand(cards));//swap 2 random DOM elements
  }

我的全局 rand 函数,从数组或数字中生成随机值

  rand = x => Array.isArray(x) ? x[rand(x.length)] : 0 | x * Math.random(),

额外挑战

如果您正确地进行了基于事件的编程,
然后用三张匹配的卡片创建记忆游戏又是小菜一碟

.. 或 4 ... 或 N 匹配的卡片