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

React中如何更新作为props传给子组件的函数内的状态值

问题根因

你现在遇到的问题来自3个核心代码错误:

  1. 反模式存储函数引用:你把onResize回调直接存入了selectorGroup状态中,每次父组件重新生成updateSelector函数时,已经存在状态数组里的旧函数引用不会自动更新,存量子组件初始化时拿到的旧引用永远不会变。
  2. 状态更新逻辑错误:现有updateSelector中filter返回的是匹配项数组而非单个对象,splice会原地修改原数组破坏React状态不可变原则,最后返回[group]还额外套了一层数组,完全不符合状态更新要求。
  3. 子组件闭包锁死旧值:子组件里的handleResize用useCallback时传了空依赖数组,直接把第一次渲染拿到的旧onResize锁死在闭包里,就算父组件传了新函数进来,事件触发时调用的还是旧值。
可直接落地的修复方案

1. 不要把回调函数存入组件状态

onResize属于父组件的逻辑方法,不需要跟着选择器的业务数据一起存在状态里,只需要在渲染子组件时,把当前最新的updateSelector作为props单独传入即可。
修改新建选择器的逻辑,删除存函数的代码:

selectorRef.current = {
  id: `${selectorGroup.length}`,
  top,
  left,
  width: 50,
  height: 50,
  lines: [],
  containerCoords,
  // 删掉 onResize: updateSelector 这行
};

渲染子组件时单独传回调,同时注意不要用数组下标当key,改用选择器唯一id,避免列表更新时组件复用导致props错乱:

{selectorGroup.map((selector) => (
  <Child key={selector.id} {...selector} onResize={updateSelector} />
))}

2. 重写updateSelector,保证函数引用稳定

用React状态的函数式更新写法,不需要把selectorGroup放入依赖数组,从根源避免函数频繁重新生成,同时修复之前的数组操作bug:

const updateSelector = useCallback(
  (updated) => {
    selectorRef.current = updated;
    setSelectorGroup((group) => {
      // 用find找单个匹配项,不要用filter返回数组
      const oldItem = group.find((selec) => selec.id === updated.id);
      if (!oldItem) return group;
      const newItem = { ...oldItem, ...updated };
      // 用map返回新数组,不要用splice原地修改原数组
      return group.map(item => item.id === updated.id ? newItem : item);
    });
  },
  // 依赖为空,这个函数引用永远不会变
  []
);

同步修改新建选择器的鼠标按下事件,避免闭包拿到旧的数组长度导致id重复:

const handleContainerMouseDown = useCallback(
  (e) => {
    e.preventDefault();
    const { ctrlKey } = e;
    if (ctrlKey) {
      const top = Number(e.clientY - containerCoords.top);
      const left = Number(e.clientX - containerCoords.left);
      // 通过状态更新函数的入参拿最新的数组值,不依赖闭包
      setSelectorGroup((group) => {
        const newSelector = {
          id: `${group.length}`,
          top,
          left,
          width: 50,
          height: 50,
          lines: [],
          containerCoords,
        };
        selectorRef.current = newSelector;
        return [...group, newSelector];
      });
    }
  },
  [containerCoords]
);

3. 修复子组件闭包问题

子组件用ref存最新的回调函数,保证原生事件触发时拿到的永远是最新传入的onResize,避免空依赖锁死旧值:

// 新增ref存最新回调
const onResizeRef = useRef(onResize);
// 每次props更新就同步最新值到ref
useEffect(() => {
  onResizeRef.current = onResize;
}, [onResize]);

const handleResize = useCallback((e) => {
  if (resizerRef && resizerRef.current) {
    const selectorEl = resizerRef.current.parentElement.parentElement;
    const selectorCoords = selectorEl.getBoundingClientRect();
    const mouseX = e.pageX - containerCoords.left;
    const vectorX = mouseX - selectorCoords.left;
    const newLeft = selectorCoords.left + vectorX;
    // 调用ref里存的最新回调
    onResizeRef.current({
      id: selectorEl.id,
      top: selectorCoords.top,
      left: newLeft,
      width: 50,
      height: 50,
    });
  }
}, [containerCoords]); // 补全依赖

同理,子组件里绑定的mouseup等全局事件如果用了useCallback,要么补全依赖数组,要么用同样的ref方式存最新函数,避免闭包旧值问题。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 13:54:21