React中如何更新作为props传给子组件的函数内的状态值
问题根因
你现在遇到的问题来自3个核心代码错误:
- 反模式存储函数引用:你把
onResize回调直接存入了selectorGroup状态中,每次父组件重新生成updateSelector函数时,已经存在状态数组里的旧函数引用不会自动更新,存量子组件初始化时拿到的旧引用永远不会变。 - 状态更新逻辑错误:现有
updateSelector中filter返回的是匹配项数组而非单个对象,splice会原地修改原数组破坏React状态不可变原则,最后返回[group]还额外套了一层数组,完全不符合状态更新要求。 - 子组件闭包锁死旧值:子组件里的
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
相关产品推荐
相关产品推荐

