如何基于React组件rc属性动态推导Ref类型?
基于ReactElement动态推导Ref类型的多态组件实现
我们在开发多态组件时,希望基于传入的rc(ReactElement)属性动态推导domRef的类型。当前实现的类型逻辑存在问题:比如传入<div/>时,domRef应该对应Ref<HTMLDivElement>,但代码并未正确触发类型错误。以下是简化的示例代码:
import React from 'react'; import { ComponentPropsWithRef, ElementType, ReactElement, ReactNode, Ref, useRef, cloneElement } from 'react'; type DomPropsWithDomRef<TElement extends ElementType> = { [Key in keyof ComponentPropsWithRef<TElement> as Key extends 'ref' ? 'domRef' : Key]: ComponentPropsWithRef<TElement>[Key]; }; type BodyProps = { rc?: ReactElement<unknown>; children?: ReactNode; } // @TODO: this isnt quite working right. :thinking_face: type GetNodeType<T> = T extends ReactElement<any, infer NodeType> ? NodeType : never; function Body<RC extends ReactElement | undefined>({ rc = <span />, children, domRef, ...props }: RC extends undefined ? DomPropsWithDomRef<'span'> & BodyProps : BodyProps & { rc: RC; domRef?: Ref<GetNodeType<RC>> }) { return cloneElement(rc, { ...props, ...(domRef ? { ref: domRef } : {}), }, children); } function App() { const bodyRef = useRef<HTMLSpanElement>(null); return <Body domRef={bodyRef} rc={<div />}>some body text</Body> // should error, because domRef should be Ref<HTMLDivElement> }
这个需求完全可以实现,问题出在泛型约束和类型推导逻辑的精准度上。以下是修正后的实现方案:
修正后的代码
import React from 'react'; import { ComponentPropsWithRef, ElementType, ReactElement, ReactNode, Ref, useRef, cloneElement } from 'react'; // 重命名原组件的ref为domRef,保留其他props type DomPropsWithDomRef<TElement extends ElementType> = Omit<ComponentPropsWithRef<TElement>, 'ref'> & { domRef?: Ref<InstanceType<TElement>>; }; // 区分传入/未传入rc的Props类型 type BodyProps<RC extends ReactElement | undefined = undefined> = RC extends undefined ? DomPropsWithDomRef<'span'> & { rc?: never; children?: ReactNode } : Omit<ComponentPropsWithRef<RC['type']>, 'ref' | 'children'> & { rc: RC; domRef?: Ref<InstanceType<RC['type']>>; children?: ReactNode; }; function Body<RC extends ReactElement | undefined = undefined>({ rc = <span /> as RC extends undefined ? ReactElement<'span'> : RC, children, domRef, ...props }: BodyProps<RC>) { return cloneElement(rc, { ...props, ...(domRef ? { ref: domRef } : {}), }, children); } // 测试验证 function App() { // 未传入rc时,domRef必须是HTMLSpanElement类型 const spanRef = useRef<HTMLSpanElement>(null); <Body domRef={spanRef}>默认span内容</Body>; // 类型正确 // 传入<div/>时,domRef必须匹配HTMLDivElement类型 const divRef = useRef<HTMLDivElement>(null); const wrongRef = useRef<HTMLSpanElement>(null); <Body domRef={divRef} rc={<div />}>div内容</Body>; // 类型正确 <Body domRef={wrongRef} rc={<div />}>div内容</Body>; // 触发类型错误,符合预期 }
关键修正说明
- 精准提取元素类型:通过
RC['type']直接获取传入rc的元素类型,再用InstanceType<T>推导对应的DOM实例类型,确保domRef与rc的类型强绑定。 - 区分两种场景的Props:将泛型默认值设为
undefined,明确区分默认使用<span/>和传入自定义rc的场景,让TypeScript能精准推导类型。 - 修复默认值类型断言:给默认的
<span/>添加类型断言,确保未传入rc时泛型能正确指向'span'类型。
修改后,当传入不同的rc元素时,domRef会自动匹配对应的DOM实例类型,类型不匹配时会触发TypeScript错误,完全满足需求。
内容的提问来源于stack exchange,提问作者glo
相关产品推荐
相关产品推荐

