嵌套的Web组件和事件处理

时间:2019-01-02 06:54:22

标签: javascript web-component dom-events custom-element

我正在用JavaScript编写记忆游戏。我制作了一个用于卡片的Web组件<memory-card>,并制作了一个包含卡片并处理游戏状态<memory-game>的Web组件。 <memory-card>类包含其翻转时的图像路径,显示在卡背面的默认图像,翻转状态以及onclick函数,用于处理状态和图像之间的切换。

<memory-game>类具有一个setter,该setter接收要从中生成<memory-cards>的图像数组。在<memory-game>类中处理游戏状态更新的最佳方法是什么?我应该在此处的<memory-card>元素上附加一个事件侦听器,还是有更好的解决方法?我希望<memory-card>元素像现在一样仅处理其自身的功能,即根据单击时的状态更改图像。

memory-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)

memory-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回答后实施自定义事件

memory-card.js(提取)

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

memory-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)
    })
  }

3 个答案:

答案 0 :(得分:5)

<memory-card>中:

  • 使用CustomEvent()创建并使用dispatchEvent()调度自定义事件

<memory-game>中:

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

由于纸牌嵌套在游戏中,因此该事件自然会冒泡到容器中。

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

答案 1 :(得分:1)

非常很有帮助,以查看一些现有代码以了解您尝试过的内容。但是如果没有它,您可以执行@Supersharp提出的建议,或者可以让<memory-game>类处理所有事件。

如果您采用这种方式,那么<memory-card>的代码将在整个字段上监听click事件。它将检查您是否单击了仍然朝下的卡片,如果是,则告诉卡片翻转。 (通过设置属性或属性,或通过在<memory-card>元素上调用函数。)

所有其他逻辑将存在于<memory-game>类中,以确定所选择的两个卡是否相同并分配点等。

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

然后,<memory-game>类将侦听flipped事件并根据该信息采取行动。

但是,这并不是一个真正的问题。这就是您要编写代码的方式以及代码的绑定方式。如果您从不打算在任何其他游戏中使用此代码,那就没关系了。

答案 2 :(得分:1)

Supersharps答案不是100%正确。

click事件使DOM泛滥, 但是CustomEvents 不要

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

应对基于事件的编程挑战

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

首先不需要this.cards,因为[...this.children]中所有卡都可用

!请记住,在JavaScript对象中是通过引用传递的,因此您的this.cards指向确切的相同 DOM子级

您在这里有依赖性,
游戏需要了解Card中的.flipCard方法。

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

提示:每张卡都需要在游戏DOM级别“监听”才能接收冒泡事件

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

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

卡负责监听正确的EventListener
(附加到card.parentNode

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

DOM是您的数据结构

如果您的游戏不再关心它拥有多少个DOM子代,
而且它不会对已有元素进行任何记账,
洗牌变成小菜一碟:

  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个匹配卡