React组件点击Button two后重渲染但useEffect不执行的原因咨询
React useState与useEffect行为疑问解答
组件代码
function Test() { const [data, setData] = useState<Array<string>>([]); console.log('Hello:', data); useEffect(() => { console.log('data: ', data) }, [data]) const test1 = () => { setData(data.concat('a')); } const test2 = () => { setData(data); } return ( <> <button onClick={test1}>Button one</button> <button onClick={test2}>Button two</button> </> ); }
问题现象
点击Button one时行为符合预期:组件重新渲染,useEffect也会执行。但Button two的行为存在两个疑问:
- 点击Button one后立即点击Button two,组件会重新渲染,但useEffect不执行。React通过
Object.is比较来决定重渲染/执行effect,为何useState和useEffect的比较结果存在差异?既然组件决定重渲染,说明state值data已改变,但setData(data)操作怎么会让data发生变化?而不执行effect又说明data依赖未变化,这两个决定显然矛盾。 - 点击Button one后第二次点击Button two,组件无任何反应,这完全符合预期。
原因解释
这事儿核心是React的更新逻辑、闭包捕获值,以及开发环境严格模式的特殊行为共同导致的,拆解来看:
第一次点击Button two的现象解析
- 闭包捕获的state值:点击Button one后,组件重新渲染,此时
test2函数已经捕获了当前的data值——也就是concat生成的新数组['a']。 - React的state对比逻辑:点击Button two调用
setData(data)时,React会用Object.is对比传入的新值和内部维护的当前state。因为两者是同一个数组引用,所以判定为相等,正常情况下不会触发组件重渲染。 - “重渲染”的真相:你看到的组件重渲染,是开发环境下React严格模式的特殊行为——严格模式会在开发阶段重复调用组件的render函数,用来检测是否存在不纯的渲染逻辑,但这并不是状态更新触发的真正重渲染,生产环境下不会出现这个现象。
- useEffect不执行的原因:useEffect的依赖数组对比同样使用
Object.is,由于新旧data是同一个数组引用,判定依赖没有变化,所以不会触发effect重新执行。
第二次点击Button two无反应的原因
第一次点击Button two时,React已经记录了这次更新的新旧值相等,后续再调用相同的setData(data)时,会直接跳过整个更新流程,自然不会有任何渲染或effect执行的行为。
额外补充:React中只有当setState传入的新值与旧值通过Object.is对比不相等时,才会触发生产环境下的真正组件重渲染。你这里的setData(data)传入的是和当前state完全相同的引用,所以正常情况下不会触发重渲染,开发环境的严格模式只是个特例。
内容的提问来源于stack exchange,提问作者Unknown developer
相关产品推荐
相关产品推荐

