如何使用TypeScript扩展Headless UI的Combobox组件?
封装Headless UI的Combobox为自定义SuperBox组件时,若传入接收activeOption的函数作为children,会触发TypeScript错误:
Type '({ activeOption }: { activeOption: any; }) => Element' is not assignable to type 'ReactNode'
原因是自定义组件中错误地将children类型声明为ReactNode,而Headless UI的Combobox原生支持Render Prop模式(即children可以是函数),原组件的children类型包含了函数形式,手动声明ReactNode会覆盖这个类型,导致类型不匹配。
修复方案
直接继承Headless UI Combobox的完整Props类型,不要手动覆盖children的类型定义:
import * as Headless from '@headlessui/react' import { ComponentProps } from 'react' // 继承原Combobox的所有Props,添加自定义className(原Props已包含className,可按需保留) interface SuperBoxProps extends ComponentProps<typeof Headless.Combobox> { className?: string } export function SuperBox({ className, children, ...props }: SuperBoxProps) { return ( <Headless.Combobox data-slot="control" className={className} {...props} > <div className="relative">{children}</div> </Headless.Combobox> ) }
原理说明
ComponentProps<typeof Headless.Combobox>自动包含了原组件的所有类型定义,其中children的类型并非单纯的ReactNode,而是兼容了ReactNode | ((props: ComboboxRenderProps) => ReactNode)(ComboboxRenderProps包含activeOption等渲染参数)。通过直接继承这个类型,自定义组件的children会自动支持函数形式的Render Prop,无需额外声明。
可选:显式指定Render Prop类型
如果需要更清晰地展示类型,可以手动提取原组件的Render Prop类型:
import * as Headless from '@headlessui/react' import { ComponentProps, ReactNode } from 'react' // 提取Combobox的渲染参数类型 type ComboboxRenderProps = Parameters<NonNullable<ComponentProps<typeof Headless.Combobox>['children']>>[0] interface SuperBoxProps extends Omit<ComponentProps<typeof Headless.Combobox>, 'children'> { className?: string children?: ReactNode | ((props: ComboboxRenderProps) => ReactNode) } export function SuperBox({ className, children, ...props }: SuperBoxProps) { return ( <Headless.Combobox data-slot="control" className={className} {...props} > <div className="relative">{children}</div> </Headless.Combobox> ) }
修改后,将原示例中的<Headless.Combobox>替换为<SuperBox>,TypeScript错误即可消除,同时保留原组件的所有功能。
内容的提问来源于stack exchange,提问作者Ben

