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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 04:13:09