如何用TypeScript泛型解决React不同元素的Ref类型兼容问题?
React + TypeScript 泛型组件Ref类型不匹配问题解决
问题场景
我尝试实现以下简化版组件,根据multiline属性值渲染<input>或<textarea>,计划通过TypeScript泛型传入HTMLInputElement或HTMLTextAreaElement来指定inputRef的类型:
import React, { ReactElement } from 'react'; import { LegacyRef } from 'react'; export interface TestProps<T> { inputRef?: LegacyRef<T>; multiline?: boolean; } export const Test = <T,>({ inputRef, multiline }: TestProps<T>): ReactElement | null => ( <> {multiline ? <textarea ref={inputRef} /> : <input ref={inputRef} type="text" />} </> );
但TypeScript编译器无法识别inputRef会随multiline值对应不同类型,在ref行出现如下报错:
Type 'LegacyRef<T>' is not assignable to type 'LegacyRef<HTMLTextAreaElement>' Type 'LegacyRef<T>' is not assignable to type 'LegacyRef<HTMLInputElement>'
解决方案
方案1:用条件类型关联泛型与multiline属性
通过条件类型让泛型T的类型与multiline的布尔值强关联,让编译器能准确推断对应Ref类型:
import React, { ReactElement, LegacyRef } from 'react'; // 条件类型:multiline为true时T必须是HTMLTextAreaElement,否则为HTMLInputElement type TestProps<T extends HTMLInputElement | HTMLTextAreaElement> = T extends HTMLTextAreaElement ? { inputRef?: LegacyRef<T>; multiline: true } : { inputRef?: LegacyRef<T>; multiline?: false }; export const Test = <T extends HTMLInputElement | HTMLTextAreaElement>( { inputRef, multiline }: TestProps<T> ): ReactElement | null => ( <> {multiline ? ( <textarea ref={inputRef as LegacyRef<HTMLTextAreaElement>} /> ) : ( <input ref={inputRef as LegacyRef<HTMLInputElement>} type="text" /> )} </> );
方案2:使用函数重载明确参数类型
通过函数重载定义不同multiline值对应的参数结构,让类型检查更精准:
import React, { ReactElement, LegacyRef } from 'react'; // 重载1:multiline为true时,Ref对应HTMLTextAreaElement function Test(props: { inputRef?: LegacyRef<HTMLTextAreaElement>; multiline: true }): ReactElement | null; // 重载2:multiline为false或省略时,Ref对应HTMLInputElement function Test(props: { inputRef?: LegacyRef<HTMLInputElement>; multiline?: false }): ReactElement | null; // 组件实现 function Test({ inputRef, multiline }: { inputRef?: LegacyRef<any>; multiline?: boolean }): ReactElement | null { return ( <> {multiline ? <textarea ref={inputRef} /> : <input ref={inputRef} type="text" />} </> ); } export default Test;
方案3:分支内直接类型断言
如果不想修改Props结构,可在渲染分支中对inputRef做类型断言,绕过编译器的类型检查(需确保使用时multiline与泛型T匹配):
import React, { ReactElement, LegacyRef } from 'react'; export interface TestProps<T extends HTMLInputElement | HTMLTextAreaElement> { inputRef?: LegacyRef<T>; multiline?: boolean; } export const Test = <T extends HTMLInputElement | HTMLTextAreaElement>( { inputRef, multiline }: TestProps<T> ): ReactElement | null => ( <> {multiline ? ( <textarea ref={inputRef as LegacyRef<HTMLTextAreaElement>} /> ) : ( <input ref={inputRef as LegacyRef<HTMLInputElement>} type="text" /> )} </> );
内容的提问来源于stack exchange,提问作者David Jentjens
相关产品推荐
相关产品推荐

