React中如何结合refs与useEffect处理ref.current可用时的代码执行
解决React中ref.current可用时执行代码的问题
你的核心问题是ref.current的变化不会触发组件重渲染,所以依赖lastEl.current的useEffect只会在组件初始挂载时执行一次,此时DOM还未完成渲染,lastEl.current自然为null。以下是几种可靠的解决方案:
方案1:监听数据源变化(适配你的业务场景)
由于绑定ref的元素依赖items渲染,可将items作为useEffect的依赖项。当异步请求完成、items更新触发组件重渲染后,ref会被正确赋值,此时执行相关逻辑:
function Feed() { const [items, setItems] = useState<IItem[]>([]) const lastEl = useRef<HTMLDivElement>(null) useEffect(() => { const lastFeedEl = lastEl.current if (!lastFeedEl) return const feedObserver = new IntersectionObserver(handleLastItemDisplay) feedObserver.observe(lastFeedEl) // 清理观察者,避免内存泄漏 return () => { feedObserver.unobserve(lastFeedEl) } }, [items]) // 依赖items,数据加载完成后触发 useEffect(() => { (async () => { const fetchedItems = await fetchItems() setItems(fetchedItems) })() }, []) return ( <div> {items.slice(0, 4).map((item, idx) => { if (idx === 2) return <div key={item.id} ref={lastEl}> <Item item={item} /> </div> return <Item key={item.id} item={item} /> })} </div> ); } export default Feed;
方案2:使用回调式Ref(精准捕捉元素挂载时机)
如果不想依赖数据源状态,或元素渲染逻辑更复杂,可使用回调式ref。它会在元素挂载到DOM时自动触发回调,直接获取到可用的DOM元素:
function Feed() { const [items, setItems] = useState<IItem[]>([]) let feedObserver: IntersectionObserver | null = null // 回调式ref,元素挂载/卸载时自动执行 const setLastEl = (el: HTMLDivElement | null) => { if (!el) { // 元素卸载时清理观察者 if (feedObserver) { feedObserver.disconnect() feedObserver = null } return } feedObserver = new IntersectionObserver(handleLastItemDisplay) feedObserver.observe(el) } useEffect(() => { (async () => { const fetchedItems = await fetchItems() setItems(fetchedItems) })() }, []) return ( <div> {items.slice(0, 4).map((item, idx) => { if (idx === 2) return <div key={item.id} ref={setLastEl}> <Item item={item} /> </div> return <Item key={item.id} item={item} /> })} </div> ); } export default Feed;
方案3:使用useLayoutEffect(DOM更新后立即执行)
如果需要在DOM渲染完成后立刻获取ref并执行逻辑,可使用useLayoutEffect,它会在DOM更新完成后同步执行,比useEffect的执行时机更早:
function Feed() { const [items, setItems] = useState<IItem[]>([]) const lastEl = useRef<HTMLDivElement>(null) useLayoutEffect(() => { const lastFeedEl = lastEl.current if (!lastFeedEl) return const feedObserver = new IntersectionObserver(handleLastItemDisplay) feedObserver.observe(lastFeedEl) return () => { feedObserver.unobserve(lastFeedEl) } }, [items]) useEffect(() => { (async () => { const fetchedItems = await fetchItems() setItems(fetchedItems) })() }, []) return ( <div> {items.slice(0, 4).map((item, idx) => { if (idx === 2) return <div key={item.id} ref={lastEl}> <Item item={item} /> </div> return <Item key={item.id} item={item} /> })} </div> ); } export default Feed;
注意事项
- 无论使用哪种方案,都必须清理IntersectionObserver实例,避免内存泄漏。
- 回调式ref是最直接的实现方式,能精准对应元素的挂载/卸载生命周期,无需依赖其他状态。
内容的提问来源于stack exchange,提问作者KotRog
相关产品推荐
相关产品推荐

