传递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
相关产品推荐
相关产品推荐

