React中ForwardedRef与RefObject的区别及适配问题
React ForwardedRef 与 RefObject 类型不匹配问题解析
问题代码示例
export const ScrollUtilsProvider = React.forwardRef<HTMLDivElement, ScrollUtilsProviderProps>( (props, ref) => { const scrollUtils = useScrollUtils(ref) // 此处出现报错 return <div ref={ref}><ScrollUtilsContext.Provider value={scrollUtils}>{props.children}</ScrollUtilsContext.Provider></div> }, ) export const useScrollUtils = <T extends Element>(ref: RefObject<T>) => { return { // 基于传入ref实现的功能函数 } }
报错信息
Argument of type 'ForwardedRef<HTMLDivElement>' is not assignable to parameter of type 'RefObject<HTMLDivElement>'. Type 'null' is not assignable to type 'RefObject<HTMLDivElement>'.
类型定义对比
// 来自@types/react@17.0.0 interface RefObject<T> { readonly current: T | null; } type ForwardedRef<T> = ((instance: T | null) => void) | MutableRefObject<T | null> | null;
问题解答
1. 为何这两种类型差异如此之大?
RefObject 是 React 通过 useRef 或 createRef 创建的固定结构对象,核心是只读的 current 属性,专门用于存储 DOM 元素或组件实例的引用,属于静态的引用存储容器。
而 ForwardedRef 是为 forwardRef 量身设计的类型,它需要兼容父组件传递 ref 的三种常见场景:
- 回调函数式 ref(比如
(el) => { ... }) - MutableRefObject(
useRef创建的可修改 ref 对象) - 未传入任何 ref(即 null)
这种设计是为了让 forwardRef 能灵活接收父组件传递的各种 ref 形式,所以类型范围比 RefObject 宽泛得多。
2. 是否可以将 ForwardedRef 当作 RefObject 使用?
直接使用不行,因为 ForwardedRef 可能是函数或 null,不符合 RefObject 的固定结构要求。但可以通过转换逻辑适配:
你可以创建一个内部的 RefObject,再同步这个内部 ref 和 ForwardedRef 的更新状态,示例代码如下:
export const ScrollUtilsProvider = React.forwardRef<HTMLDivElement, ScrollUtilsProviderProps>( (props, ref) => { const internalRef = useRef<HTMLDivElement>(null); const scrollUtils = useScrollUtils(internalRef); // 同步内部ref与转发ref的状态 useEffect(() => { if (typeof ref === 'function') { ref(internalRef.current); } else if (ref != null) { ref.current = internalRef.current; } }, [ref, internalRef.current]); return ( <div ref={internalRef}> <ScrollUtilsContext.Provider value={scrollUtils}> {props.children} </ScrollUtilsContext.Provider> </div> ); }, );
这样既满足了 useScrollUtils 对 RefObject 的参数要求,又能正确将内部 DOM 引用转发给父组件传入的 ForwardedRef。
3. ForwardedRef 有何特殊之处?
ForwardedRef 的特殊之处在于它是多态的 ref 传递管道,而非存储引用的容器:
- 它允许父组件以任意形式(回调、对象、null)传递 ref
- 核心作用是让子组件能把内部 DOM 或组件实例的引用暴露给父组件
- 本身不持有引用,仅负责将引用传递给父组件指定的接收方式
而 RefObject 是单一职责的引用存储容器,只能用来保存引用,无法直接作为 forwardRef 的参数类型使用。
内容的提问来源于stack exchange,提问作者Yehoshua Oliveira
相关产品推荐
相关产品推荐

