所以在点击时,它只能将一张卡翻转回来。所以在我的函数中正确地读取了toggle,但我不明白为什么它没有把两张牌都翻过来,它只翻转了我的数组中位置[1]的牌,而位置[0]是不受影响的,当只是翻转牌时,在翻转函数中,它把两张牌都翻转了,但似乎当我设置数组时,我漏掉了什么。此外,如果我试图做一些像cardChoiceId[0].setToggle(false)这样的事情,我得到一个类型错误,说不是一个函数,所以这不是解决方案,下面是代码
import bg from '../images/card-bg.png'
let cardChoiceId = []
let cardsWon = []
const GameCard = ({ img, name, num, setScore, setResult, cardArray }) => {
const [toggle, setToggle] = useState(false)
const checkForMatch = () => {
if (cardChoiceId[0] === cardChoiceId[1]) {
alert(`🌱That's a Match🌱`)
setToggle(true)
cardsWon.push(cardChoiceId)
cardChoiceId = []
setScore(+1)
if (cardsWon.length === cardArray.length) {
setResult(`🎆 Congratz!!! You Win 🎆`)
}
}
else {
setToggle(false)
cardChoiceId = []
}
}
const flip = () => {
setToggle(!false)
cardChoiceId.push(name)
console.log(cardChoiceId)
if (cardChoiceId.length === 2) {
return setTimeout(checkForMatch, 500)
}
}
return toggle === false ? (
<div onClick={flip}>
<div className='game-card-bg' >
<img src={bg} alt='card' style={{ width: '100%' }} />
</div>
</div>
):(
<div>
<div className='game-card' >
<img src={img} alt='card' />
<h4 className='card-name'>{name}</h4>
</div>
</div>
)
}
export default GameCard
问题在于,你试图在本地组件状态下管理游戏状态,并将组件状态模式与全局状态(``cardChoiceId`等)混合。让我把话说得更明白些,你应该遵循的是 分清主次 原则。
你有 游戏 国家应该跟踪有多少张牌是toggeledflipped并检查它们是否匹配。
你有 本地 国情 GameCard
组件,负责渲染游戏卡的任一状态(切换或不切换)。首先,看起来你的本地状态应该管理切换状态,但你的全局状态取决于切换状态,所以它必须被 "拉升"。因此,在 GameCard
.
我更建议外部组件应该告诉它是否被翻转,同时外部组件也应该提供一个回调,组件可以用它来通知外部组件一个点击事件。请注意,如何推理每个组件发生的事情变得更加容易。
首先,想一想一张卡可以处于的状态,对我来说,这将是:
const CardState = {
HIDDEN: 0,
FLIPPED: 1,
WON: 2,
};
让我们假设一个... cardArray
洗牌后的牌阵,每个名字都出现两次
[
{
id: 1,
name: 'Tiger',
img: '...',
},
{
id: 2,
name: 'Lion',
img: '...',
},
{
id: 3,
name: 'Tiger',
img: '...',
},
{
id: 4,
name: 'Lion',
img: '...',
},
...
]
现在,我们可以实现我们的 Game
逻辑,但我们并不关心如何显示卡片本身。我们只关心这里的逻辑。
const Game = ({ cardArray }) => {
// keep track of every cards state
const [cardState, setCardState] = useState(
// initialize every card as HIDDEN
cardArray.reduce((a, c) => ({
...a,
[c.id]: {
...c, // all card info
state: CardState.HIDDEN, // add state
},
}, {})
);
// set the new state of a specific card
const updateState = (id, newState) => setCardState(prev => {
...prev, // keep state of all cards
[id]: {
...prev[id],
state: newState, // update state of id
},
});
// check if all cards are WON
const checkGameOver = () => {
const notWonCard = Object.values(cardState)
.find(card => card.state !== CardState.WON);
if (!notWonCard /* === all won*/) {
alert(`🎆 Congratz!!! You Win 🎆`);
}
};
// checks if two FLIPPED cards match and if so, set them both WON, in this case also trigger a checkGameOver
const checkForMatch = () => {
const flippedCards = Object.values(cardState)
.filter(card => card.state === CardState.FLIPPED);
if (flippedCards.length === 2) {
if (flippedCards[0].name === flippedCards[1].name) {
alert(`🌱That's a Match🌱`);
updateState(flippedCards[0].id, CardState.WON);
updateState(flippedCards[1].id, CardState.WON);
setTimeout(checkGameOver, 500);
}
}
};
// update the state of a card depending on its current state and trigger checkForMatch
const onCardClick = id => {
switch (cardState[id].state) {
case CardState.FLIPPED: {
updateState(id, CardState.HIDDEN);
break;
}
case CardState.HIDDEN: {
updateState(id, CardState.FLIPPED);
setTimeout(checkForMatch, 500);
break;
}
case CardState.WON:
// fallthrough
default: {
// noop
}
}
};
return (
<div>
{cardArray.map(card => (
<GameCard
key={card.id}
name={card.name}
img={card.img}
isFlipped={
cardState[card.id] === CardState.FLIPPED
|| cardState[card.id] === CardState.WON
}
onClick={() => onCardClick(card.id)}
/>
))}
</div>
);
}
现在逻辑已经完成了,我们可以不用管它,去想实际的卡片应该如何渲染。
import bg from '...';
const GameCard = ({ name, img, isFlipped, onClick }) => (
isFlipped
? (
<div>
<div className='game-card' >
<img src={img} alt='card' />
<h4 className='card-name'>{name}</h4>
</div>
</div>
) : (
<div onClick={onClick}>
<div className='game-card-bg' >
<img src={bg} alt='card' style={{ width: '100%' }} />
</div>
</div>
)
);
这不是经过测试的代码,而是给你一个分离关注点的概念。