You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在记忆游戏场景中避免使用带依赖项的useEffect钩子

问题

我正在开发一款简易记忆游戏应用,想了解在以下场景中是否可避免使用带依赖项的useEffect钩子:用户点击卡片时触发函数,将点击的卡片存入两个状态变量之一,后续用于比对。

点击处理函数代码:

const handleClick = (card: Card) => {
  if (card.id !== choiceOne?.id) {
    if (choiceOne) return setChoiceTwo(card)
    return setChoiceOne(card)
  }
}

当前实现中,当choiceTwo更新时,useEffect会触发执行卡片比对及其他必要操作,代码如下:

const useAsyncUseEffect = (fn: () => void, depends: Parameters<typeof useEffect>[1]) => {
  useEffect(() => {
    fn()
  }, [depends])
}

useAsyncUseEffect(async () => {
  if (choiceOne && choiceTwo) {
    setDisabled(true)
    if (choiceOne.src === choiceTwo.src) {
      setMatchCount(prev => prev + 1)
      setCards(prev =>
        prev.map(card => (card.src === choiceOne.src ? { ...card, match: true } : card))
      )
      resetSelection()
    } else {
      await sleep(1000)
      resetSelection()
    }
  }
}, [choiceTwo])
解决方案

完全可以避免使用useEffect,把比对逻辑直接整合到点击处理函数里就行,这样代码逻辑更连贯,也不用依赖状态更新触发副作用。

修改后的点击处理函数可以写成这样:

const handleClick = async (card: Card) => {
  // 点击已选中的卡片、已匹配的卡片或处于禁用状态时直接返回
  if (card.id === choiceOne?.id || card.match || disabled) return

  // 处理第一次选择
  if (!choiceOne) {
    setChoiceOne(card)
    return
  }

  // 处理第二次选择
  setChoiceTwo(card)
  setDisabled(true)

  // 比对两张卡片
  if (choiceOne.src === card.src) {
    setMatchCount(prev => prev + 1)
    setCards(prev =>
      prev.map(cardItem => (cardItem.src === choiceOne.src ? { ...cardItem, match: true } : cardItem))
    )
    resetSelection()
    setDisabled(false)
  } else {
    await sleep(1000)
    resetSelection()
    setDisabled(false)
  }
}

说明

  • 把原本在useEffect里的异步逻辑直接放到handleClick中,用户完成第二次点击时立刻执行比对,不需要等状态更新触发副作用。
  • 增加了对disabled和card.match的判断,避免在卡片比对过程中重复点击,逻辑更严谨。
  • 移除了自定义的useAsyncUseEffect和相关副作用依赖,代码结构更简洁,状态变化的因果关系更清晰。

内容的提问来源于stack exchange,提问作者ReanuKeeves

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 03:40:21