React中useEffect仅在后续更新时触发DOM更新的问题排查与解决求助
嘿,我完全懂你这种困惑!刚上手React的useEffect时,这种和引用类型state相关的坑真的很容易踩中,我来帮你把问题拆解清楚,再给你一个非hack的解决方案。
问题的核心原因:你修改了state的原引用,React没检测到变化
你看这段代码:
let newBoard = board
这里的newBoard其实和board指向的是同一个数组引用。当你在answer.map里修改newBoard[index] = letter时,你其实直接修改了原state数组的内容,但React的state更新机制是依赖引用变化来判断是否需要重渲染的。
所以当你调用setBoard(newBoard)时,因为数组的引用没改变,React会认为state没有变化,也就不会触发组件重渲染。直到你调用setState(state + 1)修改了state这个状态,组件才会重新渲染,这时候之前修改的数组内容才会显示在DOM上——这就是为什么你要等下一次输入才看到上一次的结果。
非hack的正确解决方式:创建数组的新引用
要让React检测到state变化,你需要创建一个全新的数组副本,而不是复用原引用。有几种简单的方式:
- 使用扩展运算符创建新数组:
let newBoard = [...board]
- 使用
Array.from创建副本:
let newBoard = Array.from(board)
把这行代码替换掉原来的let newBoard = board,然后去掉那个setState(state + 1)的hack,你的组件就能正常更新了。
额外的优化建议
还有个小细节要注意:当你更新count的时候,因为它依赖之前的count状态,最好使用函数式更新,这样可以避免闭包导致的旧值问题:
if (miss) { setCount(prevCount => prevCount + 1) }
修改后的Game.jsx关键代码
const [board, setBoard] = useState(new Array(answer.length).fill(' ')) const [guess, setGuess] = useState('') const [count, setCount] = useState(0) // 可以删掉state这个状态了 // const [state, setState] = useState(0) useEffect(() => { let miss = true // 创建新数组副本 let newBoard = [...board] answer.map((letter, index) => { if (guess === letter) { newBoard[index] = letter miss = false } }) if (miss) { // 函数式更新count setCount(prevCount => prevCount + 1) } setBoard(newBoard) }, [guess])
这样修改后,每次用户输入 guess,useEffect都会创建新的数组副本,修改后通过setBoard更新state,React能检测到引用变化,就会立即触发DOM更新啦!
备注:内容来源于stack exchange,提问作者Jillian Hoenig

