在 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()````