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

React中useState初始化写法差异致状态异常的疑问

翻牌匹配游戏卡牌重置问题解析

问题背景

我正在开发一款翻牌匹配游戏,需要先将所有卡牌洗牌,保证每次游戏的卡牌布局不同。但用setInterval()实现游戏计时器时,App组件每秒都会重渲染,导致随机卡牌数组不断重置;点击或其他触发重渲染的操作也会引发同样问题。

原代码(存在问题)

const cardsIconArray = [{
icon: (<span className="material-symbols-outlined">
  settings_accessibility
</span>),
flipped: false},{
icon: (<span className="material-symbols-outlined">
  terminal
</span>),
flipped: false},...]

const renderedIconArray = [...cardsIconArray, ...cardsIconArray]

function shuffle(array) {
  let currentIndex = array.length, randomIndex;
  while (currentIndex > 0) {
    randomIndex = Math.floor(Math.random() * currentIndex);
    currentIndex--;
    [array[currentIndex], array[randomIndex]] = [
      array[randomIndex], array[currentIndex]];
  }
  return array;
}


function App() {
  const [cards, setCards] = useState(
    shuffle(renderedIconArray)
  );
//后续对`cards`进行操作...
}

运行后发现cards变量会随App组件每秒重渲染而刷新,不符合预期。

修改后的代码(问题解决)

function App() {
  const [cards, setCards] = useState(
    shuffle([...cardsIconArray, ...cardsIconArray])
  );
}

疑问

仅将数组拼接操作[...cardsIconArray, ...cardsIconArray]移至useState()内部而非提前合并数组,为什么结果会不同?


原因解析

核心差异在于数组引用特性和**useState初始值的执行规则**,结合你的shuffle函数逻辑拆解:

  1. 原代码的问题本质

    • renderedIconArray是组件外部的模块级变量,只会初始化一次,始终指向同一个数组引用。
    • 你的shuffle函数是原地修改数组:它直接交换传入数组的元素位置,没有创建新数组,最后返回的还是原数组的引用。
    • 第一次渲染时,useState(shuffle(renderedIconArray))会打乱renderedIconArray,并把这个数组的引用作为cards的状态保存。
    • 组件每次重渲染时,都会再次执行shuffle(renderedIconArray)——此时传入的还是那个共享的renderedIconArray,shuffle会再次原地打乱它。而cards状态和renderedIconArray是同一个引用,所以cards的内容会被意外修改,看起来像是“重置”了。
  2. 修改后代码的解决逻辑

    • 把[...cardsIconArray, ...cardsIconArray]移到useState内部后,每次执行这个表达式都会创建一个全新的数组(和外部任何数组无引用关联)。
    • shuffle会修改这个临时新数组,useState在第一次渲染时将这个新数组的引用作为状态保存。
    • 后续组件重渲染时,虽然shuffle([...cardsIconArray, ...cardsIconArray])仍会执行,但useState的规则是初始值仅在组件第一次挂载时生效,后续重渲染会直接复用之前保存的状态值。而且因为每次创建的是新数组,即使shuffle执行,也不会影响已保存在状态里的cards数组,所以状态不会被意外修改。

更严谨的优化写法

为了避免useState初始值表达式在重渲染时做无意义计算,推荐用useEffect确保洗牌逻辑只执行一次:

function App() {
  const [cards, setCards] = useState([]);

  useEffect(() => {
    // 每次挂载时创建新数组并洗牌
    const newCards = shuffle([...cardsIconArray, ...cardsIconArray]);
    setCards(newCards);
  }, []); // 空依赖数组,仅在组件挂载时执行

  // 后续逻辑...
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 09:30:56