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
相关产品推荐
相关产品推荐

