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

如何让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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 22:38:11