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

React泛型组件组合:如何让Autocomplete子组件共用同一类型参数?

如何让组合式Autocomplete组件共用同一类型参数?

我采用组件组合模式设计Autocomplete组件,期望的使用方式如下:

<Autocomplete.Root value={currentValue} onChange={handleChange}>
    <Autocomplete.Input />
    <Autocomplete.Dropdown>
        {options.map((option) => (
            <Autocomplete.Option value={option.value}>{option.label}</Autocomplete.Option>
        )}
    </Autocomplete.Dropdown>
</Autocomplete.Root>

其中Autocomplete.Root、Autocomplete.Input、Autocomplete.Option这三个组件需要共享同一个类型参数T(值的类型)。但目前它们各自独立指定类型参数,无法校验一致性,会出现如下错误用法:

<Autocomplete.Root value={123} onChange={handleChange}>
    <Autocomplete.Input />
    <Autocomplete.Dropdown>
        <Autocomplete.Option value="Some string value">{option.label}</Autocomplete.Option>
    </Autocomplete.Dropdown>
</Autocomplete.Root>

这种类型不匹配的情况会导致严重的运行时错误。

我尝试过用类来封装带类型参数的组件集合:

export class Autocomplete<T> {
    public Root = AutocompleteRoot<T>;
    public Input = AutocompleteInput<T>;
    public Dropdown = AutocompleteDropdown;
    public Option = AutocompleteOption<T>;
}

想通过type StringAutocomplete = Autocomplete<string>;来复用,但React不允许在JSX中直接使用类实例的属性作为组件。请问有没有办法让这些组件共用同一类型参数?


方案1:工厂函数生成绑定类型的组件集合

写一个工厂函数,调用时传入类型T(TypeScript也能自动推导),返回一套绑定了该类型的子组件集合。这样用的时候,只需要创建对应类型的Autocomplete实例,所有子组件都会自动遵循同一类型约束。

示例代码:

import { AutocompleteRoot, AutocompleteInput, AutocompleteDropdown, AutocompleteOption } from './components';

function createAutocomplete<T>() {
  return {
    Root: AutocompleteRoot<T>,
    Input: AutocompleteInput<T>,
    Dropdown: AutocompleteDropdown,
    Option: AutocompleteOption<T>,
  };
}

// 创建对应类型的组件集合
const NumberAutocomplete = createAutocomplete<number>();
const StringAutocomplete = createAutocomplete<string>();

// JSX中使用
function App() {
  const [value, setValue] = useState<number>(123);
  const options = [{ value: 123, label: '123' }, { value: 456, label: '456' }];

  return (
    <NumberAutocomplete.Root value={value} onChange={setValue}>
      <NumberAutocomplete.Input />
      <NumberAutocomplete.Dropdown>
        {options.map(opt => (
          <NumberAutocomplete.Option key={opt.value} value={opt.value}>
            {opt.label}
          </NumberAutocomplete.Option>
        ))}
      </NumberAutocomplete.Dropdown>
    </NumberAutocomplete.Root>
  );
}

方案2:用Context传递类型约束

借助React Context在组件树中传递类型信息,子组件通过Context获取类型来约束自身参数。这种方式能保留你原本想要的Autocomplete.Root写法,不用额外创建组件集合。

示例代码:

import { createContext, useContext } from 'react';

// 仅用于传递类型的Context,不需要实际值
type AutocompleteContextType<T> = { __type: T };
const AutocompleteContext = createContext<AutocompleteContextType<any>>({ __type: null });

// 改造Root组件,把类型注入Context
function AutocompleteRoot<T>({ value, onChange, children }: { value: T; onChange: (val: T) => void; children: React.ReactNode }) {
  return (
    <AutocompleteContext.Provider value={{ __type: value }}>
      <div className="autocomplete-root">
        {children}
      </div>
    </AutocompleteContext.Provider>
  );
}

// 改造Input组件,从Context拿类型约束
function AutocompleteInput() {
  const ctx = useContext(AutocompleteContext);
  type T = typeof ctx.__type;
  // 这里可以根据T约束Input的相关属性逻辑
  return <input className="autocomplete-input" />;
}

// 改造Option组件,校验类型一致性
function AutocompleteOption<T>({ value, children }: { value: T; children: React.ReactNode }) {
  const ctx = useContext(AutocompleteContext);
  // 确保传入的value和Root的类型一致
  const typedValue = value as typeof ctx.__type;
  return <div className="autocomplete-option" data-value={typedValue}>{children}</div>;
}

// 导出组合组件
const Autocomplete = {
  Root: AutocompleteRoot,
  Input: AutocompleteInput,
  Dropdown: AutocompleteDropdown,
  Option: AutocompleteOption,
};

// 使用时TypeScript会自动校验类型匹配
function App() {
  const [value, setValue] = useState<string>('abc');
  const options = [{ value: 'abc', label: 'ABC' }, { value: 'def', label: 'DEF' }];

  return (
    <Autocomplete.Root value={value} onChange={setValue}>
      <Autocomplete.Input />
      <Autocomplete.Dropdown>
        {options.map(opt => (
          <Autocomplete.Option key={opt.value} value={opt.value}>
            {opt.label}
          </Autocomplete.Option>
        ))}
      </Autocomplete.Dropdown>
    </Autocomplete.Root>
  );
}

方案3:泛型类型别名+组件包装

如果想贴近类的写法,用类型别名定义泛型组件集合,再通过函数返回实例,本质和方案1类似,但写法更接近你之前尝试的类思路:

// 定义泛型组件集合类型
type Autocomplete<T> = {
  Root: typeof AutocompleteRoot<T>;
  Input: typeof AutocompleteInput<T>;
  Dropdown: typeof AutocompleteDropdown;
  Option: typeof AutocompleteOption<T>;
};

// 返回绑定类型的组件集合
function getAutocomplete<T>(): Autocomplete<T> {
  return {
    Root: AutocompleteRoot<T>,
    Input: AutocompleteInput<T>,
    Dropdown: AutocompleteDropdown,
    Option: AutocompleteOption<T>,
  };
}

// 使用方式
const StringAutocomplete = getAutocomplete<string>();
// JSX中用法和方案1一致

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 20:55:43