TypeScript泛型推断问题:createRoute的props回调参数类型异常
问题背景
我正在为TypeScript函数createRoute定义泛型:
export function createRoute< const TOptions, const TComponent extends Component >(options: TOptions & CreateRouteOptions & WithComponent<TComponent, TOptions>): Route<TOptions> { ... }
相关类型定义如下:
CreateRouteOptions是路由基础属性类型:
export type CreateRouteOptions = { name: string, path: string, query: string, }
WithComponent包含组件及props回调上下文:
export type WithComponent< TComponent extends Component = Component, TOptions = unknown > = { component: TComponent, props?: (route: TOptions) => ComponentProps<TComponent>, }
createRoute仅接收一个对象参数,TOptions本应匹配CreateRouteOptions,但如果把泛型设为const TOptions extends CreateRouteOptions会破坏WithComponent的类型推导。目前大部分功能正常,但props回调的route参数类型是unknown,而非预期的具体字面量类型(比如{ name: 'foo', path: '/', query: '' }),我搞不懂TypeScript是怎么推导TOptions类型的。
示例代码:
const myRoute = createRoute({ name: 'foo', path: '/', query: '', component: SomeComponent, props: (route) => { ... } // ^? 预期类型:{ name: 'foo', path: '/', query: '' } 实际类型:unknown })
完整可运行示例:
type Component = { template: string, script: string, props: Record<string, any> } type ComponentProps<T> = T extends { props: infer TProps extends Record<string, any> } ? TProps : {} const SomeComponent = { template: 'something', script: '', props: { foo: true, bar: 123 } } satisfies Component type Route<T> = T & {id: number} type CreateRouteOptions = { name: string, path: string, query: string, } export type WithComponent< TComponent, TOptions > = { component: TComponent, props?: (route: TOptions) => ComponentProps<TComponent>, } function createRoute< const TOptions, const TComponent >(options: TOptions & CreateRouteOptions & WithComponent<TComponent, TOptions>): Route<TOptions> { throw 'not implemented' } const myRoute = createRoute({ name: 'foo', path: '/', query: '', component: SomeComponent, props: (route) => ({ foo: true, bar: 14 }) // ^? 预期类型:{ name: 'foo', path: '/', query: '' } 实际类型:unknown })
问题分析
当前泛型约束的核心问题是:TOptions和WithComponent<TComponent, TOptions>形成了循环依赖,TypeScript无法在推导TOptions的同时解析props回调的参数类型,只能默认设为unknown。另外,直接给TOptions加extends CreateRouteOptions会让TypeScript将其推断为CreateRouteOptions基类型,而非传入的具体字面量值。
解决方案
调整泛型结构,先让TypeScript捕获TOptions的完整字面量类型,再确保它符合CreateRouteOptions约束,同时关联WithComponent的类型推导:
type CreateRouteProps<TOptions extends CreateRouteOptions, TComponent> = TOptions & WithComponent<TComponent, TOptions>; function createRoute< const TOptions extends CreateRouteOptions, const TComponent extends Component >(options: CreateRouteProps<TOptions, TComponent>): Route<TOptions> { throw 'not implemented' }
通过拆分类型组合,让TOptions先被推断为具体的字面量类型,再代入WithComponent中,这样props回调的route参数就能正确关联到TOptions的具体值。
验证效果
修改后,示例中的route参数类型会被正确推断为{ name: 'foo', path: '/', query: '' },完全符合预期。
内容的提问来源于stack exchange,提问作者stackoverfloweth

