在 React 中切换而不是切换所有元素
Toggle in React not Toggling all elements
所以点击它只会切换一张卡片。所以切换在我的函数中被正确读取,但我不明白为什么它没有翻转两张卡片它只翻转我数组中位置 [1] 的卡片而位置 [0] 不受影响,当只是翻转卡片时 false in翻转功能可以翻转两张卡片,但是当我设置数组时,我似乎遗漏了一些东西。另外,如果我尝试执行类似 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` 等)混合。让我说得更清楚,你应该遵循seperation of concerns原则。
你得到了 游戏 状态,它应该跟踪有多少张牌 toggeled/flipped 并检查它们是否匹配。
您获得 local 组件的 GameCard
状态,该组件负责在任一状态(切换或未切换)下呈现游戏卡。首先,您的本地状态似乎应该管理切换状态,但您的全局状态取决于切换状态,因此它必须是 'pulled up'。因此,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>
)
);
它不是经过测试的代码,但应该让您了解关注点分离。
我只能提供工作代码认为这是另一个答案...所以很抱歉,这就是为什么确保您立即在 public 论坛上回答问题很重要.. ..但这是工作代码,不确定我是如何解决的,稍后我有更多时间时可能会编辑它....
棋盘逻辑
const [cards, setCards] = useState(props.cards)
const [checkers, setCheckers] = useState([])
const [completed, setCompleted] = useState([])
const onCardClick = card => () => {
if (checkersFull(checkers) || cardAlreadyInCheckers(checkers, card)) return
const newCheckers = [...checkers, card]
setCheckers(newCheckers)
const cardsInCheckersMatched = validateCheckers(newCheckers)
if (cardsInCheckersMatched) {
setCompleted([...completed, newCheckers[0].type])
}
if (checkersFull(newCheckers)) {
resetCheckersAfter(1000)
}
function validateCheckers(checkers){
return checkers.length === 2 &&
checkers[0].type === checkers[1].type
}
function cardAlreadyInCheckers(checkers, card){
return checkers.length === 1 && checkers[0].id === card.id
}
function checkersFull(checkers){
return checkers.length === 2
}
function resetCheckersAfter(time) {
setTimeout(() => {
setCheckers([])
}, time)
}
}
useEffect(() => {
const newCards = cards.map(card => ({
...card,
flipped:
checkers.find(c => c.id === card.id) ||
completed.includes(card.type),
}))
setCards(newCards)
//eslint-disable-next-line
}, [checkers, completed])
return (
<div className="Board">
{cards.map(card => (
<Card {...card} onClick={onCardClick(card)} key={card.id} />
))}
</div>
)
}
卡片逻辑
const images = {duck, pig, horse, cat, dog, dolphin, fish, roo, lion, frog }
const cards = Object.keys(images).reduce((result, item) => {
const getCard = () => ({
id: id++,
type: item,
backImg,
frontImg: images[item],
flipped: false,
})
return [...result, getCard(), getCard()]
}, [])
return shuffle(cards)
}
const shuffle=(arr) =>{
let len = arr.length
for (let i = 0; i < len; i++) {
let randomIdx = Math.floor(Math.random() * len)
let copyCurrent = { ...arr[i] }
let copyRandom = { ...arr[randomIdx] }
arr[i] = copyRandom
arr[randomIdx] = copyCurrent
}
return arr
}
const cards = buildCards()````
所以点击它只会切换一张卡片。所以切换在我的函数中被正确读取,但我不明白为什么它没有翻转两张卡片它只翻转我数组中位置 [1] 的卡片而位置 [0] 不受影响,当只是翻转卡片时 false in翻转功能可以翻转两张卡片,但是当我设置数组时,我似乎遗漏了一些东西。另外,如果我尝试执行类似 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` 等)混合。让我说得更清楚,你应该遵循seperation of concerns原则。
你得到了 游戏 状态,它应该跟踪有多少张牌 toggeled/flipped 并检查它们是否匹配。
您获得 local 组件的 GameCard
状态,该组件负责在任一状态(切换或未切换)下呈现游戏卡。首先,您的本地状态似乎应该管理切换状态,但您的全局状态取决于切换状态,因此它必须是 'pulled up'。因此,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>
)
);
它不是经过测试的代码,但应该让您了解关注点分离。
我只能提供工作代码认为这是另一个答案...所以很抱歉,这就是为什么确保您立即在 public 论坛上回答问题很重要.. ..但这是工作代码,不确定我是如何解决的,稍后我有更多时间时可能会编辑它....
棋盘逻辑
const [cards, setCards] = useState(props.cards)
const [checkers, setCheckers] = useState([])
const [completed, setCompleted] = useState([])
const onCardClick = card => () => {
if (checkersFull(checkers) || cardAlreadyInCheckers(checkers, card)) return
const newCheckers = [...checkers, card]
setCheckers(newCheckers)
const cardsInCheckersMatched = validateCheckers(newCheckers)
if (cardsInCheckersMatched) {
setCompleted([...completed, newCheckers[0].type])
}
if (checkersFull(newCheckers)) {
resetCheckersAfter(1000)
}
function validateCheckers(checkers){
return checkers.length === 2 &&
checkers[0].type === checkers[1].type
}
function cardAlreadyInCheckers(checkers, card){
return checkers.length === 1 && checkers[0].id === card.id
}
function checkersFull(checkers){
return checkers.length === 2
}
function resetCheckersAfter(time) {
setTimeout(() => {
setCheckers([])
}, time)
}
}
useEffect(() => {
const newCards = cards.map(card => ({
...card,
flipped:
checkers.find(c => c.id === card.id) ||
completed.includes(card.type),
}))
setCards(newCards)
//eslint-disable-next-line
}, [checkers, completed])
return (
<div className="Board">
{cards.map(card => (
<Card {...card} onClick={onCardClick(card)} key={card.id} />
))}
</div>
)
}
卡片逻辑
const images = {duck, pig, horse, cat, dog, dolphin, fish, roo, lion, frog }
const cards = Object.keys(images).reduce((result, item) => {
const getCard = () => ({
id: id++,
type: item,
backImg,
frontImg: images[item],
flipped: false,
})
return [...result, getCard(), getCard()]
}, [])
return shuffle(cards)
}
const shuffle=(arr) =>{
let len = arr.length
for (let i = 0; i < len; i++) {
let randomIdx = Math.floor(Math.random() * len)
let copyCurrent = { ...arr[i] }
let copyRandom = { ...arr[randomIdx] }
arr[i] = copyRandom
arr[randomIdx] = copyCurrent
}
return arr
}
const cards = buildCards()````