如何让TSX中的泛型Combobox组件正常工作?
泛型Combobox组件类型定义问题
我正在实现一个带泛型的Combobox组件,但始终无法搞定类型定义,代码如下:
import { clsx } from 'clsx'; import { useCombobox } from 'downshift'; import type { ForwardedRef, ReactNode } from 'react'; import { forwardRef } from 'react'; import { Input } from '../../atoms/Input/Input'; import './Combobox.css'; import { ComboboxProvider } from './ComboboxContext'; import { ComboboxList } from './ComboboxList'; import { ComboboxListItem } from './ComboboxListItem'; import type { ComboboxProps } from './types'; /** * ComboBox * * @description A component with a dropdown, search and filter functionality * * @example * <Combobox> * <Combobox.Prefix> * <Search /> * </Combobox.Prefix> * <Combobox.List> * <Combobox.Item> * <Item /> * </Combobox.Item> * <Combobox.Item> * <Item /> * </Combobox.Item> * <Combobox.Item> * <Item /> * </Combobox.Item> * <Combobox.Item> * <Item /> * </Combobox.Item> * </Combobox.List> * <Combobox.Suffix> * <Close /> * </Combobox.Suffix> * </Combobox> */ const Combobox = forwardRef( <Item,>( { placeholder, name, id, defaultValue, className, children, ...props }: ComboboxProps<Item>, ref: ForwardedRef<HTMLInputElement>, ) => { const { getInputProps, ...rest } = useCombobox<Item>(props); return ( <Input {...getInputProps({ name, id, placeholder, defaultValue, ref })} className={clsx('combobox', className)} > <ComboboxProvider {...rest}>{children}</ComboboxProvider> </Input> ); }, ); const CompoundCombobox = <Item,>() => Object.assign(Combobox<Item>, { Prefix: Input.Prefix, Suffix: Input.Suffix, List: ComboboxList, Item: ComboboxListItem, }); export { CompoundCombobox as Combobox };
在Object.assign(Combobox<Item>, {这一行的泛型处报错,错误信息:
Type 'ForwardRefExoticComponent<Pick<InputProps, "className" | "children" | "placeholder" | "defaultValue" | "name" | "id"> & UseComboboxProps & RefAttributes>' has no signatures for which the type argument list is applicable.
ComboboxProps的定义:
type ComboboxProps<Item> = Pick< InputProps, 'className' | 'children' | 'placeholder' | 'defaultValue' | 'name' | 'id' > & UseComboboxProps<Item>;
我期望的组件使用方式:
<Combobox<ComboboxItemType> items={items} placeholder="Combobox" id="combobox" name="combobox" itemToString={itemToString} onInputValueChange={({ inputValue }) => { items.filter((el) => filter(el, inputValue)); }} > <Combobox.Prefix> <Search /> </Combobox.Prefix> <Combobox.List> {items.map((item, index) => ( <Combobox.Item key={index} index={index} item={item}> <div style={{ display: 'flex', flexDirection: 'row', justifyContent: 'space-between', width: '100%', }} > <span>{item.title}</span> <span style={{ color: 'var(--neutral-800)' }}>{item.category}</span> </div> </Combobox.Item> ))} </Combobox.List> </Combobox>
解决方案
问题出在CompoundCombobox的实现逻辑上:forwardRef返回的ForwardRefExoticComponent不能直接通过<Item>传入泛型参数,因为它本身不是泛型函数,而是带有泛型约束的组件类型。
修改后的完整代码
import { clsx } from 'clsx'; import { useCombobox, UseComboboxProps } from 'downshift'; import type { ForwardedRef, ReactNode } from 'react'; import { forwardRef } from 'react'; import { Input } from '../../atoms/Input/Input'; import './Combobox.css'; import { ComboboxProvider } from './ComboboxContext'; import { ComboboxList } from './ComboboxList'; import { ComboboxListItem } from './ComboboxListItem'; import type { ComboboxProps } from './types'; /** * ComboBox * * @description 带下拉、搜索和筛选功能的组件 * * @example * <Combobox<ItemType> items={items}> * <Combobox.Prefix> * <Search /> * </Combobox.Prefix> * <Combobox.List> * {items.map(item => ( * <Combobox.Item key={item.id} item={item}> * {item.label} * </Combobox.Item> * ))} * </Combobox.List> * </Combobox> */ // 定义基础泛型组件并添加类型断言,明确它支持泛型参数 const BaseCombobox = forwardRef(<Item,>( { placeholder, name, id, defaultValue, className, children, ...props }: ComboboxProps<Item>, ref: ForwardedRef<HTMLInputElement>, ) => { const { getInputProps, ...rest } = useCombobox<Item>(props); return ( <Input {...getInputProps({ name, id, placeholder, defaultValue, ref })} className={clsx('combobox', className)} > <ComboboxProvider {...rest}>{children}</ComboboxProvider> </Input> ); }) as <Item>(props: ComboboxProps<Item> & { ref?: ForwardedRef<HTMLInputElement> }) => JSX.Element; // 合并基础组件与复合成员的类型,直接挂载静态属性 const Combobox = BaseCombobox as typeof BaseCombobox & { Prefix: typeof Input.Prefix; Suffix: typeof Input.Suffix; List: typeof ComboboxList; Item: typeof ComboboxListItem; }; Combobox.Prefix = Input.Prefix; Combobox.Suffix = Input.Suffix; Combobox.List = ComboboxList; Combobox.Item = ComboboxListItem; export { Combobox };
关键修改点
- 给
forwardRef返回的组件添加类型断言,明确它是一个泛型组件,让TypeScript识别其泛型参数支持。 - 放弃
Object.assign动态生成的方式,直接给基础组件挂载静态成员,通过类型合并(&)定义复合组件的完整类型。 - 这种写法既保留了复合组件的结构,又能让TypeScript正确处理泛型参数的类型检查。
可选补充:ListItem泛型支持
如果ComboboxListItem也需要依赖泛型Item,可以同步修改其定义:
// 更新ComboboxListItem为泛型组件 export const ComboboxListItem = <Item,>({ item, index, children }: { item: Item; index: number; children?: ReactNode }) => { // 组件逻辑实现 return <li>{children}</li>; }; // 同步更新Combobox的类型定义 const Combobox = BaseCombobox as typeof BaseCombobox & { Prefix: typeof Input.Prefix; Suffix: typeof Input.Suffix; List: typeof ComboboxList; Item: typeof ComboboxListItem; };
修改后即可按照预期的方式使用泛型Combobox组件,类型检查完全正常。
内容的提问来源于stack exchange,提问作者Mout Pessemier
相关产品推荐
相关产品推荐

