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

传递index参数给ref时触发无限循环的原因排查

为什么传递index给ref回调会触发无限循环?

先看你的两段代码对比:

正常运行的代码:

const container = useCallback((node: any) => {
  console.log(node);
}, []);
return (
  <>
    {reports.map((report, i) => (
      <Resizer ref={container} key={i}>
        {report}
      </Resizer>
    ))}
  </>
);

--> 此代码运行正常,但未传递index参数

触发无限循环的代码:

const container = useCallback((node: any, index: number) => {
  console.log(index, node);
}, []);
return (
  <>
    {reports.map((report, i) => (
      <Resizer ref={(node) => container(node, i)} key={i}>
        {report}
      </Resizer>
    ))}
  </>
);

--> 触发无限循环!!

问题根源:内联ref函数引发的重渲染循环

这里的核心问题出在你传递给<Resizer>的ref是一个每次渲染都会重新创建的内联箭头函数,而非useCallback稳定化后的container函数本身:

  1. 当你用(node) => container(node, i)作为ref时,组件每一次渲染(比如reports变化、父组件状态更新触发的渲染),map循环里的每一个<Resizer>都会生成一个全新的箭头函数实例。
  2. React在处理ref时会严格对比引用:如果前后两次渲染的ref不是同一个函数,React会先调用旧的ref函数传入null(清理之前的节点引用),再调用新的ref函数传入当前DOM节点。
  3. 如果<Resizer>组件内部对ref的变化做出了响应(比如在ref更新时触发状态变更、或执行了导致组件重渲染的逻辑),或者父组件因为<Resizer>的重渲染而再次触发自身渲染,就会形成渲染→创建新ref→Resizer重渲染→父组件重渲染→创建新ref的无限循环。

而第一段代码中,你直接传递的是useCallback包裹的container函数——这个函数的引用在组件生命周期内是稳定的(因为useCallback的依赖数组为空),所以React不会重复执行ref的清理和重新绑定,自然不会触发额外的重渲染循环。

可行解决方案(不使用全局变量)

方式1:为每个索引创建稳定的回调函数

用useMemo配合useCallback,为每个索引生成一个稳定的回调,避免每次渲染创建新函数:

const containerCallbacks = useMemo(() => {
  return reports.map((_, i) => 
    useCallback((node: any) => {
      console.log(i, node);
    }, [])
  );
}, [reports]);

return (
  <>
    {reports.map((report, i) => (
      <Resizer ref={containerCallbacks[i]} key={i}>
        {report}
      </Resizer>
    ))}
  </>
);

这里useMemo会在reports变化时重新生成回调数组,每个回调都是useCallback稳定化后的实例,不会触发不必要的ref更新。

方式2:用ref对象存储索引与节点的映射

如果不需要在ref回调中立即处理逻辑,也可以用一个ref对象来统一管理所有节点和对应的索引:

const nodesRef = useRef<Map<number, any>>(new Map());

const container = useCallback((node: any, index: number) => {
  if (node) {
    nodesRef.current.set(index, node);
  } else {
    nodesRef.current.delete(index);
  }
  console.log(index, node);
}, []);

return (
  <>
    {reports.map((report, i) => (
      <Resizer ref={(node) => container(node, i)} key={i}>
        {report}
      </Resizer>
    ))}
  </>
);

这个方式虽然仍用了内联函数,但如果<Resizer>不会因为ref变化触发重渲染,就不会产生循环。不过更稳妥的还是方式1。

内容的提问来源于stack exchange,提问作者Junhoflow

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 18:45:49