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

React组件中Ref<HTMLElement>与LegacyRef<HTMLLIElement>类型不兼容问题

React标签页组件TypeRef类型不匹配错误修复

问题代码

我实现了一个标签页组件:

function Tab({
      children,
      className,
      fullWidth = true,
      ...props
    }: ExtractProps<typeof HeadlessTab> & {
      fullWidth?: boolean;
    }) {
      const list = useContext(listContext);
      const liRef = React.createRef<HTMLLIElement>()
      return (
        <HeadlessTab as={Fragment}>
        {({ selected }) => (
          <li
          ref={liRef}
            className={twMerge([
              "focus-visible:outline-none",
              fullWidth && "flex-1",
              list.variant == "tabs" && "-mb-px",
            ])}
            {...props}
          >
            <tabContext.Provider
              value={{
                selected: selected,
              }}
            >
              {typeof children === "function"
                ? children({
                    selected: selected,
                  })
                : children}
            </tabContext.Provider>
          </li>
        )}
      </HeadlessTab>
      );
    }

错误信息

运行时触发TypeScript类型错误:

TS2322: Type 'Ref<HTMLElement>' is not assignable to type 'LegacyRef<HTMLLIElement> | undefined'.
  Type 'RefObject<HTMLElement>' is not assignable to type 'LegacyRef<HTMLLIElement> | undefined'.
    Type 'RefObject<HTMLElement>' is not assignable to type 'RefObject<HTMLLIElement>'.
      Type 'HTMLElement' is missing the following properties from type 'HTMLLIElement': type, value
    32 |     {({ selected }) => (
    33 |       <li
  > 34 |       ref={liRef}
       |       ^^^
    35 |         className={twMerge([
    36 |           "focus-visible:outline-none",
    37 |           fullWidth && "flex-1",

尝试重新指定liRef为HTMLLIElement类型后问题仍未解决。

问题根源

ExtractProps<typeof HeadlessTab>中包含了类型为Ref<HTMLElement>的ref属性,当你通过...props将外部传入的ref展开到<li>元素时,它会和你内部定义的liRef产生冲突。TypeScript检测到外部ref的类型是HTMLLIElement的父类型HTMLElement,但<li>需要严格匹配的HTMLLIElement类型,因此抛出类型不匹配错误。

解决方案

方案1:合并内部ref与外部ref(保留内部ref使用)

使用forwardRef接收外部ref,并创建合并函数同时管理内部和外部ref:

import React, { Fragment, useContext, forwardRef, useRef } from 'react';

const Tab = forwardRef<HTMLLIElement, ExtractProps<typeof HeadlessTab> & { fullWidth?: boolean }>(
  ({ children, className, fullWidth = true, ...props }, externalRef) => {
    const list = useContext(listContext);
    const liRef = useRef<HTMLLIElement>(null);

    // 合并内部ref和外部ref
    const combinedRef = (element: HTMLLIElement | null) => {
      liRef.current = element;
      if (typeof externalRef === 'function') {
        externalRef(element);
      } else if (externalRef) {
        (externalRef as React.MutableRefObject<HTMLLIElement | null>).current = element;
      }
    };

    return (
      <HeadlessTab as={Fragment}>
        {({ selected }) => (
          <li
            ref={combinedRef}
            className={twMerge([
              "focus-visible:outline-none",
              fullWidth && "flex-1",
              list.variant === "tabs" && "-mb-px",
            ])}
            {...props}
          >
            <tabContext.Provider value={{ selected }}>
              {typeof children === "function" ? children({ selected }) : children}
            </tabContext.Provider>
          </li>
        )}
      </HeadlessTab>
    );
  }
);

Tab.displayName = 'Tab';
export default Tab;

方案2:直接转发外部ref(无需内部使用ref)

如果不需要在组件内部使用liRef,可以简化代码,直接将外部ref绑定到<li>:

import React, { Fragment, useContext, forwardRef } from 'react';

const Tab = forwardRef<HTMLLIElement, ExtractProps<typeof HeadlessTab> & { fullWidth?: boolean }>(
  ({ children, className, fullWidth = true, ...props }, ref) => {
    const list = useContext(listContext);
    return (
      <HeadlessTab as={Fragment}>
        {({ selected }) => (
          <li
            ref={ref}
            className={twMerge([
              "focus-visible:outline-none",
              fullWidth && "flex-1",
              list.variant === "tabs" && "-mb-px",
            ])}
            {...props}
          >
            <tabContext.Provider value={{ selected }}>
              {typeof children === "function" ? children({ selected }) : children}
            </tabContext.Provider>
          </li>
        )}
      </HeadlessTab>
    );
  }
);

Tab.displayName = 'Tab';
export default Tab;

关键说明

  • 使用forwardRef明确外部ref的类型为HTMLLIElement,确保与<li>元素类型严格匹配
  • 用useRef替代createRef,因为createRef在函数组件每次渲染都会生成新的ref对象,而useRef保持引用稳定
  • 避免直接在组件内部定义ref的同时展开包含外部ref的props,否则会触发类型冲突

内容的提问来源于stack exchange,提问作者Noel Omondi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 05:54:52