React forwardRef组件未接收父组件ref时,三种内部创建ref方案如何选择
方案对比与最优选择
三个方案优劣分析
- Option 1:
const divRef = ref || React.createRef();
存在严重缺陷,不可用。React.createRef()每次调用都会生成全新的ref对象,而forwardRef的渲染函数在组件每一次重渲染时都会执行。如果父组件未传递ref,每次重渲染都会生成新的divRef,导致之前存储的DOM引用丢失,内部逻辑依赖ref的话会出现不可预期的错误。 - Option 2:
是三种方案里的最优解。const _ref = useRef(null); const divRef = ref || _ref;useRef创建的ref对象在组件整个生命周期内唯一,不会随重渲染变化。父组件传递ref时直接使用父级ref,父级可以正常拿到DOM引用;未传递时使用内部稳定的ref对象,完全满足需求。唯一需要注意的是该写法仅支持父组件传递对象类型ref的场景,如果父组件传递的是函数类型ref,需要额外适配。 - Option 3:
const divRef = useRef(ref?.current);
存在逻辑缺陷,不满足需求。useRef的初始值仅在组件第一次初始化时生效,且该写法无论父组件是否传递ref,最终使用的都是组件内部创建的新ref对象,完全忽略了父级传入的ref本身。父组件传递ref时无法拿到对应的DOM引用,只有父组件未传ref的场景下看起来能正常运行。
Option2与Option3是否等价
二者完全不等价,核心差异在于对父级传入ref的处理:
- Option2在父级传ref时会直接绑定父级的ref对象,父级可正常获取DOM引用
- Option3全程使用内部创建的ref,父级传入的ref会被完全忽略,仅在父级未传ref时表现和Option2一致
更完善的通用实现
如果需要兼容父级传递函数类型ref的场景,可以使用回调ref的写法统一处理:
export const Child = React.forwardRef((props, parentRef) => { const innerRef = useRef(null); const divRef = useCallback((node) => { // 同时赋值内部ref和父级ref innerRef.current = node; if (typeof parentRef === 'function') { parentRef(node); } else if (parentRef != null) { parentRef.current = node; } }, [parentRef]); // 内部使用innerRef即可拿到DOM引用 useEffect(() => { console.log("divRef: ", innerRef.current); }, []); return <div ref={divRef}>child</div>; });
内容的提问来源于stack exchange,提问作者Lin Du
相关产品推荐
相关产品推荐

