You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何基于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 19:40:34