嵌套的网络组件和事件处理
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 匹配的卡片
我正在 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 匹配的卡片