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

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?

问题原因分析

首次与后续表现差异的核心原因

  1. 首次s变化时:
    严格模式会强制组件初始化渲染两次,每次初始渲染时lastS.current的初始值都是undefined,因此两次渲染的初始阶段都满足s !== lastS.current的条件,都会执行setWords添加word,后续状态更新后的渲染也一致。

  2. 后续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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 15:17:48