React App中触发useState的按钮多次点击后崩溃的原因排查
问题分析与修复:随机轶事按钮失效问题
问题描述
完成Full Stack Open练习时,实现了随机展示轶事、投票及显示最高票轶事的功能,但遇到异常:“下一则轶事”按钮点击几次后会停止工作,刷新页面后可点击次数不固定,点击投票按钮可能恢复正常。
错误根源
- 固定随机索引导致的失效假象:原代码中
newIndex在组件渲染时提前生成,每次组件渲染只会生成一个固定值,点击按钮时复用的是当前渲染周期的newIndex。若该值与当前selected索引重复,会导致轶事无变化,看起来像是按钮失效;同时闭包特性会让部分点击事件捕获旧的newIndex值,进一步加剧异常。 - 状态更新的潜在闭包问题:
addVote函数直接依赖当前渲染周期的votes状态,极端情况下可能无法获取最新状态,导致投票更新异常。
修复步骤
- 点击时实时生成随机索引:将随机索引的生成逻辑移到“下一则轶事”按钮的点击回调内部,确保每次点击都生成全新的随机值。
- 优化投票状态更新方式:使用React的函数式状态更新,确保获取到最新的投票状态,避免闭包导致的错误。
- 提取常量到组件外部:将轶事数组、初始投票数组等固定常量移到组件外,避免每次渲染重复创建,提升性能。
修复后的代码
import { useState } from 'react' import React from 'react' // 提取固定常量到组件外部,避免重复生成 const firstIndex = Math.floor(Math.random() * 7) const anecdotes = [ 'If it hurts, do it more often.', 'Adding manpower to a late software project makes it later!', 'The first 90 percent of the code accounts for the first 10 percent of the development time...The remaining 10 percent of the code accounts for the other 90 percent of the development time.', 'Any fool can write code that a computer can understand. Good programmers write code that humans can understand.', 'Premature optimization is the root of all evil.', 'Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.', 'Programming without an extremely heavy use of console.log is same as if a doctor would refuse to use x-rays or blood tests when diagnosing patients.' ] const anecdoteCount = anecdotes.length const initialVotes = Array(anecdoteCount).fill(0) const App = () => { const [selected, setSelected] = useState(firstIndex) const [votes, setVotes] = useState(initialVotes) const addVote = (index) => { // 函数式更新确保获取最新状态 setVotes(prevVotes => { const updatedVotes = [...prevVotes] updatedVotes[index] += 1 return updatedVotes }) } // 生成与当前索引不同的随机值(避免连续展示同一轶事) const getRandomAnecdoteIndex = () => { let newIndex do { newIndex = Math.floor(Math.random() * anecdoteCount) } while (newIndex === selected) return newIndex } const highestVoteCount = Math.max(...votes) const highestVoteIndex = votes.indexOf(highestVoteCount) return ( <React.Fragment> <h1>Anecdote of the day</h1> <h4>{anecdotes[selected]}</h4> <p>has {votes[selected]} votes</p> <button type="button" onClick={() => addVote(selected)}>vote</button> <button type="button" onClick={() => setSelected(getRandomAnecdoteIndex())}>next anecdote</button> <h1>Anecdote with most votes</h1> <p>The anecdote with most votes is: <mark>{anecdotes[highestVoteIndex]}</mark></p> <p>With "{highestVoteCount}" votes</p> </React.Fragment> ) } export default App
关键说明
- 实时生成随机索引:每次点击“下一则轶事”按钮时,都会重新生成随机索引,彻底避免了固定值导致的重复或失效问题。
- 函数式状态更新:
setVotes通过函数接收上一次的状态值,确保投票更新始终基于最新数据,避免闭包引发的状态不同步问题。 - 动态获取数组长度:用
anecdotes.length代替硬编码的7,后续修改轶事数量时无需手动调整相关逻辑。
内容的提问来源于stack exchange,提问作者Mateo
相关产品推荐
相关产品推荐

