如何通过含refs的自定义Hook复用useEffect代码?
自定义Hook中使用Ref与内联useEffect表现不一致的原因及解决方案
我需要在渲染完成后实现一些使用refs的自定义功能,这似乎是useEffect的理想应用场景,但需封装为自定义Hook以实现复用。遗憾的是,将ref或其current属性传入包含useEffect的自定义Hook时,其表现与直接调用useEffect存在差异。
可复现的示例代码:
const useCustomHookWithUseEffect = (el1, el2) => { React.useEffect(() => { console.log("CUSTOM Use effect..."); console.log("firstRef element defined", !!el1); console.log("secondRef element", !!el2); }, [el1, el2]); } const RefDemo = () => { const [vis, setVis] = React.useState(false); const firstRef = React.useRef(null); const secondRef = React.useRef(null); useCustomHookWithUseEffect(firstRef.current, secondRef.current); React.useEffect(() => { console.log("Standard Use effect..."); console.log("firstRef element defined", !!firstRef.current); console.log("secondRef element ", !!secondRef.current); }, [firstRef.current, secondRef.current]); console.log("At RefDemo render", !!firstRef.current , !!secondRef.current); return ( <div> <div ref={firstRef}> My ref is created in the initial render </div> <div className="clickme" onClick={() => setVis(true)}> click me </div> {vis && <div ref={secondRef}>boo (second ref must surely be valid now?)</div> } </div> ) }
控制台日志:
"At RefDemo render", false, false "CUSTOM Use effect..." "firstRef element defined", false "secondRef element", false "Standard Use effect..." "firstRef element defined", true "secondRef element ", false Now I click the clickme "At RefDemo render", true, false "CUSTOM Use effect..." "firstRef element defined", true "secondRef element", false "Standard Use effect..." "firstRef element defined", true "secondRef element ", true
这是预期行为吗?
是的,这完全符合React的运行机制,原因如下:
- Ref的current更新时机:React会在组件渲染完成(DOM更新后)才会把DOM元素赋值给
ref.current,而组件渲染阶段(执行RefDemo函数时),ref.current还是初始值null。 - 闭包捕获的差异:
- 自定义Hook中,你传入的是
firstRef.current和secondRef.current,也就是渲染阶段的current值,这个值会被useEffect的回调闭包捕获。即使后续ref.current被更新,回调里的el1和el2还是渲染时的旧值。 - 内联useEffect的回调里直接访问
firstRef.current,当useEffect执行时(DOM已更新),ref.current已经被赋值为DOM元素,所以能拿到最新值。
- 自定义Hook中,你传入的是
- Ref对象的稳定性:ref对象本身(
firstRef)是通过useRef创建的,在组件整个生命周期内引用不变,但ref.current的变化不会触发组件重新渲染,所以依赖项里的firstRef.current变化时,React不会重新运行自定义Hook的useEffect(除非组件因其他状态更新重新渲染)。
如何实现复用基于useEffect且使用refs的代码?
有两种可靠的方案:
方案1:传递ref对象而非current属性
直接把ref对象传给自定义Hook,在useEffect回调里实时访问ref.current,这样每次effect执行时都能拿到最新的DOM元素:
const useCustomHookWithUseEffect = (ref1, ref2) => { React.useEffect(() => { console.log("CUSTOM Use effect..."); console.log("firstRef element defined", !!ref1.current); console.log("secondRef element", !!ref2.current); }, [ref1, ref2]); // ref对象是稳定的,依赖项不会频繁变化 } // 在组件中调用时: useCustomHookWithUseEffect(firstRef, secondRef);
方案2:使用回调式Ref(监听ref的变化)
如果需要在ref被赋值/更新时立即执行逻辑,可以用回调式Ref结合useCallback,这样能精准捕获ref的变化时机:
const useCustomRefEffect = (callback) => { const ref = React.useRef(null); const savedCallback = React.useCallback(callback, [callback]); const updateRef = React.useCallback((element) => { ref.current = element; savedCallback(element); // 当ref被赋值时执行回调 }, [savedCallback]); return updateRef; } // 在组件中使用: const firstRefCallback = useCustomRefEffect((element) => { console.log("First ref updated:", !!element); }); const secondRefCallback = useCustomRefEffect((element) => { console.log("Second ref updated:", !!element); }); // 渲染时: <div ref={firstRefCallback}>...</div> {vis && <div ref={secondRefCallback}>...</div>}
这种方式能直接在ref被赋值的第一时间触发逻辑,比依赖useEffect更及时,适合需要监听ref元素创建/销毁的场景。
内容的提问来源于stack exchange,提问作者GreenAsJade
相关产品推荐
相关产品推荐

