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

