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

传递Ref导致React父组件触发所有子组件不必要重渲染的问题

原因分析
  • 当组件X的array状态更新时,X会触发重渲染,此时遍历数组生成的每个<Y>组件的ref回调都是全新的内联函数。
  • 对于React.forwardRef包装的组件,ref是独立于props的特殊参数,React.memo的默认浅比较逻辑仅对比props,不会处理ref的变化。只要ref的引用发生改变(比如新创建了函数),React就会强制触发组件重渲染,完全无视props是否未发生变化。
  • 因此每次新增数组元素时,所有Y组件的ref回调都会被重新创建,导致全部Y组件触发不必要的重渲染。
解决方案

方案一:用useMemo稳定ref回调

为每个数组元素生成稳定的ref回调函数,确保已有元素的回调引用不会随X的重渲染而改变:

修改X组件中的数组遍历部分:

{array.map((e) => {
  // 用useMemo缓存ref回调,仅当e变化时才重新创建
  const refCallback = React.useMemo(() => (no: Handle) => {
    nodes.current[e.toString()] = no;
  }, [e]);

  return (
    <Y
      ref={refCallback}
      entry={e}
      key={e}
    />
  );
})}

原理:useMemo依赖e,数组新增元素时,原有元素的e值不变,对应的refCallback引用保持稳定,Y组件的ref不会变化,React.memo会正确判断无需重渲染。

方案二:子组件主动注册ref到父组件

取消父组件传递ref回调,改为通过props传递稳定的注册函数,让子组件主动完成ref注册:

修改Y组件

type YProps = {
  entry: number;
  id: string;
  registerRef: (id: string, ref: Handle) => void;
};

const Y = React.memo(
  React.forwardRef<Handle, YProps>((props, ref) => {
    console.log("render");
    React.useImperativeHandle(
      ref,
      () => ({
        jump: () => {
          console.log("test");
        },
      }),
      []
    );

    // 组件挂载/更新时注册ref,卸载时清理
    React.useEffect(() => {
      if (ref.current) {
        props.registerRef(props.id, ref.current);
      }
      return () => {
        delete nodes.current[props.id];
      };
    }, [props.id, props.registerRef]);

    return <div>{props.entry}</div>;
  })
);

修改X组件

const X: React.FC = React.memo(() => {
  const nodes = React.useRef<{ [key: string]: Handle }>({});
  const [array, setArray] = React.useState([1, 2, 3]);

  // 用函数式更新避免依赖array,让回调更稳定
  const handleClick = React.useCallback(() => {
    setArray(prev => prev.concat(prev.length + 1));
  }, []);

  const jumpToNode = React.useCallback(() => {
    nodes.current["1"]?.jump();
  }, []);

  // 稳定的ref注册函数
  const registerRef = React.useCallback((id: string, ref: Handle) => {
    nodes.current[id] = ref;
  }, []);

  return (
    <>
      <button onClick={handleClick}>add new</button>
      <button onClick={jumpToNode}>jump to first node</button>
      {array.map((e) => (
        <Y
          id={e.toString()}
          entry={e}
          key={e}
          registerRef={registerRef}
        />
      ))}
    </>
  );
});

原理:父组件传递的registerRef是用useCallback稳定的函数,原有Y组件的props(id、entry、registerRef)都不会变化,React.memo会正确跳过不必要的重渲染。同时这种方式避免了父组件直接传递ref,逻辑更解耦。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 21:07:02