React中useRef渲染流程疑问:多次点击Show按钮后的日志解析
问题分析与解答
测试代码
function Page() { const ref = useRef(null) const [show, setShow] = useState(false); const [refHasValue, setRefHasValue] = useState(false); console.log(ref, show, refHasValue); useEffect(() => { if (ref.current) { setRefHasValue(true); } }, [ref.current]); return ( <> <button onClick={() => setShow(true)}>Show</button> {show && <div ref={ref}>Hello</div>} {refHasValue && <p>Ref has value</p>} </> ); }
控制台日志
{current: null} false false(初始渲染){current: null} true false(首次点击Show按钮后){current: div} true false(再次点击Show按钮后)
1. 为什么第三次会触发渲染?
第二次点击Show按钮时调用了setShow(true),尽管当前show状态已经是true,但对于未用React.memo包裹的函数组件来说,只要调用setState,无论新值与旧值是否相等,都会触发一次重渲染,这就是第三次渲染的直接原因。
2. 为什么useEffect监听ref.current却没更新refHasValue?
核心在于React渲染流程与ref、useEffect的执行时机:
- 首次点击Show后,
show变为true,组件进入渲染阶段:此时<div>被纳入虚拟DOM,但真实DOM尚未挂载,ref.current仍为null。渲染完成后进入commit阶段,React才会挂载真实DOM并将ref.current设置为对应的div元素。但ref是可变对象,它的变化不会触发React重渲染,因此此时不会重新执行useEffect(渲染阶段捕获的依赖是null,和初始渲染的依赖一致)。 - 第二次点击触发重渲染时,真实DOM已经存在,
ref.current在渲染阶段就已经是div元素了。此时useEffect的依赖ref.current从之前的null变为div对象,依赖发生变化,useEffect会在这次渲染完成后执行,调用setRefHasValue(true)进而触发第四次渲染(日志会显示{current: div} true true)。你看到的第三次日志只是渲染阶段的打印,此时useEffect还未执行,所以refHasValue仍然是false。
另外需要注意:useRef的current属性变化不会触发组件重渲染,这也是首次点击后ref.current变为div却没有自动触发新渲染的原因。
内容的提问来源于stack exchange,提问作者hh54188
相关产品推荐
相关产品推荐

