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

