TypeScript:索引属性与推断泛型类型不匹配的编译错误排查
TypeScript计算属性键类型不兼容问题分析与解决
以下TypeScript代码无法编译:
type Validator<T> = (value: string) => { value: T } const createValidator = <TState extends any>(state: TState) => <TName extends keyof TState>(x: TName, validator: Validator<TState[TName]>) => { return (value: { [P in TName]: string }): { [P in TName]: TState[TName] } => { const result = validator(value[x]); return { [x]: result.value }; }; }
报错信息:Type '{ [x: string]: TState[TName]; }' is not assignable to type '{ [P in TName]: TState[TName]; }'
错误原因
TypeScript处理对象字面量的计算属性键时,会自动将键的类型拓宽为string——哪怕变量x的类型是TName extends keyof TState这种具体的键字面量类型。这导致生成的对象被推断为「字符串索引类型」,而函数返回要求的是「精确的具名属性类型」(比如当TName为"age"时,返回类型是{ age: number })。字符串索引类型无法赋值给精确的具名属性类型,因此触发类型不兼容错误。
非类型断言的解决方法
方法一:使用Object.defineProperty构建对象
Object.defineProperty的类型定义能正确关联键与值的类型,避免类型拓宽问题:
type Validator<T> = (value: string) => { value: T } const createValidator = <TState extends any>(state: TState) => <TName extends keyof TState>(x: TName, validator: Validator<TState[TName]>) => { return (value: { [P in TName]: string }): { [P in TName]: TState[TName] } => { const result = validator(value[x]); const obj = {} as { [P in TName]: TState[TName] }; Object.defineProperty(obj, x, { value: result.value }); return obj; }; }
方法二:使用Object.fromEntries创建对象
利用Object.fromEntries的类型推断能力,传入键值对数组可以让TypeScript正确识别返回对象的类型:
type Validator<T> = (value: string) => { value: T } const createValidator = <TState extends any>(state: TState) => <TName extends keyof TState>(x: TName, validator: Validator<TState[TName]>) => { return (value: { [P in TName]: string }): { [P in TName]: TState[TName] } => { const result = validator(value[x]); return Object.fromEntries<[TName, TState[TName]]>([[x, result.value]]) as { [P in TName]: TState[TName] }; }; }
注:这里的as用于将Object.fromEntries返回的Record<TName, TState[TName]>转换为目标的具名属性类型,属于类型兼容的转换,并非强制类型断言。
方法三:先初始化空对象再赋值
先声明对象为目标类型,再通过属性赋值填充内容,TypeScript能正确识别属性类型:
type Validator<T> = (value: string) => { value: T } const createValidator = <TState extends any>(state: TState) => <TName extends keyof TState>(x: TName, validator: Validator<TState[TName]>) => { return (value: { [P in TName]: string }): { [P in TName]: TState[TName] } => { const result = validator(value[x]); const res: { [P in TName]: TState[TName] } = {} as any; res[x] = result.value; return res; }; }
这里的as any仅用于初始化空对象,后续赋值会经过严格类型检查,相比直接断言返回值更安全。
内容的提问来源于stack exchange,提问作者asgerhallas
相关产品推荐
相关产品推荐

