TypeScript递归泛型类型异常原因及正常实现问询
泛型递归类型的类型错误原因与修复方案
问题背景
非泛型版本的递归类型可以正常进行类型提示:
type StringAndNumberForNestedSemanticTokenValue = | string | number | ({ DEFAULT?: string | number } & { [key: string]: StringAndNumberForNestedSemanticTokenValue }) export type SemanticSpacingDefinition = Record<string, StringAndNumberForNestedSemanticTokenValue> const test: SemanticSpacingDefinition = { a: { DEFAULT: 'test', b: { c: { DEFAULT: 'test', d: 'ddd', }, }, }, }
但改为泛型版本后,出现类型错误:Object literal may only specify known properties, and 'c' does not exist in type '{ DEFAULT?: string | undefined; }'
type NestedSemanticTokenValue<T> = | T | ({ DEFAULT?: T } & { [key: string]: NestedSemanticTokenValue<T> }) export type SemanticSpacingDefinition = Record<string, NestedSemanticTokenValue<string> > const test: SemanticSpacingDefinition = { a: { DEFAULT: 'test', b: { // type error c: { DEFAULT: 'test', d: 'ddd', }, }, }, }
错误原因
TypeScript对对象字面量有多余属性检查机制,在泛型版本中,类型推断时会优先匹配联合类型里的{ DEFAULT?: T }分支,而非带索引签名的交叉分支。这个分支没有定义允许额外属性的规则,所以当你给b添加c这个额外属性时,就触发了类型错误。
而非泛型版本中,TypeScript能更清晰地识别到联合类型里包含带索引签名的结构,允许添加任意字符串键,所以不会报错。
修复方案
方案一:合并属性与索引签名
直接将DEFAULT属性合并到带有索引签名的对象中,让TypeScript明确这个分支允许任意字符串键:
type NestedSemanticTokenValue<T> = | T | { DEFAULT?: T; [key: string]: NestedSemanticTokenValue<T> | undefined; } export type SemanticSpacingDefinition = Record<string, NestedSemanticTokenValue<string>> const test: SemanticSpacingDefinition = { a: { DEFAULT: 'test', b: { c: { DEFAULT: 'test', d: 'ddd', }, }, }, }
方案二:用Record替代手动索引签名
如果需要保留交叉类型的写法,可以用Record<string, ...>替代手动编写的索引签名,让TypeScript正确识别允许任意字符串键:
type NestedSemanticTokenValue<T> = | T | ({ DEFAULT?: T; } & Record<string, NestedSemanticTokenValue<T>>) export type SemanticSpacingDefinition = Record<string, NestedSemanticTokenValue<string>> const test: SemanticSpacingDefinition = { a: { DEFAULT: 'test', b: { c: { DEFAULT: 'test', d: 'ddd', }, }, }, }
内容的提问来源于stack exchange,提问作者x1ngYu
相关产品推荐
相关产品推荐

