React严格模式下的异常行为:状态更新失效问题排查
React严格模式下状态更新异常问题分析
问题背景
组件每50ms渲染一次,props中的s每秒递增1,需求是当s变化时将word添加到状态数组words中。首次s变化时,严格模式的两次渲染都能正常添加word,但后续s变化时,仅严格模式的第二次渲染无法添加内容。
组件代码
export const Component = ({ word, s }) => { const lastS = useRef() const [words, setWords] = useState() if (s !== lastS.current) { console.log('adding', word, words, s, lastS.current) setWords([...words, word]) lastS.current = s } console.log('rendered', word, words, s, lastS.current) }
首次s变化时控制台输出
1st pass : adding apple > Array[] 9 undefined // inside "if" statement 1st pass : rendered apple > Array[] 9 9 // component renders 1st pass : rendered apple > Array["apple"] 9 9 // state changed, rerender 2st pass : adding apple > Array[] 9 undefined // same as 1st 2st pass : rendered apple > Array[] 9 9 // same as 1st 2st pass : rendered apple > Array["apple"] 9 9 // same as 1st
后续s变化时控制台输出
1st pass : adding orange > Array["apple"] 10 9 1st pass : rendered orange > Array["apple"] 10 10 1st pass : rendered orange > Array["apple", "orange"] 10 10 2st pass : rendered orange > Array["apple"] 10 10 <------------ why?
问题原因分析
首次与后续表现差异的核心原因
首次
s变化时:
严格模式会强制组件初始化渲染两次,每次初始渲染时lastS.current的初始值都是undefined,因此两次渲染的初始阶段都满足s !== lastS.current的条件,都会执行setWords添加word,后续状态更新后的渲染也一致。后续
s变化时:- 第一次渲染:
lastS.current保存的是上一次的s值(比如9),当前s为10,满足条件,执行setWords并将lastS.current更新为10。状态更新后组件重新渲染,此时lastS.current等于s,不会重复执行添加逻辑。 - 第二次严格模式渲染:此时
lastS.current已经被第一次渲染修改为10,和当前s值相等,s !== lastS.current不成立,因此不会执行setWords,也就不会添加word到数组中。
- 第一次渲染:
此处的副作用是什么?
- 直接在组件渲染函数中执行状态更新(
setWords)和修改ref值(lastS.current = s),这些都是违反React渲染规则的副作用操作。 - React要求渲染函数必须是纯函数:仅根据props和state计算输出,不能有任何会影响外部状态或导致不可预测结果的操作。严格模式的两次渲染正是为了检测这类非纯渲染行为,暴露潜在的bug。
正确实现方式
将副作用逻辑移至useEffect钩子中,并使用函数式状态更新避免闭包问题:
export const Component = ({ word, s }) => { const lastS = useRef() const [words, setWords] = useState([]) // 初始化空数组,避免undefined解构报错 useEffect(() => { if (s !== lastS.current) { console.log('adding', word, words, s, lastS.current) // 函数式更新获取最新状态快照 setWords(prevWords => [...prevWords, word]) lastS.current = s } }, [s, word]) // 依赖数组包含触发副作用的变量 console.log('rendered', word, words, s, lastS.current) return <div>{words.join(', ')}</div> // 组件必须返回合法JSX }
内容的提问来源于stack exchange,提问作者Cirrocumulus
相关产品推荐
相关产品推荐

