为对象属性生成双返回变体的TS类型定义与实现问题
TypeScript 对象转换函数的类型错误解决
需求背景
输入一个对象:
const inObj = {name: 'hi'}
通过函数X处理后,返回包含Raw和Result两个变体的输出:
const out = {nameRaw: ref('hi'), nameResult: ref(ok('hi'))} // ok = neverthrow库的函数
类型定义尝试
符合预期但存在语法错误的类型定义:
type funcOuta<Type extends funcInObj> = { [Property in keyof Type as `${Property & string}Raw`]: Ref<Type[Property]> [Property in keyof Type as `${Property & string}Result`]: Ref<Result<Type[Property], Error>>; };错误原因:同一类型字面量中无法同时存在两个独立的映射类型,需用交叉类型合并。
无语法错误但类型不精准的定义:
Raw与Result类型重叠,使用时需手动类型收窄,不符合需求:type funcOutb<Type extends DtoUscResp> = { [Property in keyof Type as `${Property & string}${'Raw' | 'Result'}`]: | Ref<Type[Property]> | Ref<Result<Type[Property], Error>>; };已解决的精准类型定义方案(感谢@tobias):通过交叉类型实现
Raw和Result各自对应正确类型type funcOut<Type> = { [Property in keyof Type as `${Property & string}Raw`]: Type[Property] } & { [Property in keyof Type as `${Property & string}Result`]: Array<Type[Property]> };测试类型
test = funcOut<{name:' test'}>可得到预期的精准类型。
未解决问题:函数实现的TS索引错误
实现完整可运行示例时,出现以下TS错误:
Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'funcOut
'. No index signature with a parameter of type 'string' was found on type 'funcOut '
完整代码:
type funcArg = {[index: string] : any} type input = funcArg & {name: string} // {name:hi} type funcOut<Type> = { [Property in keyof Type as `${Property & string}Raw`]: Type[Property] } & { [Property in keyof Type as `${Property & string}Result`]: Array<Type[Property]> } type test = funcOut<{name:' test'}> // 类型符合预期,感谢@Tobias function X<Type extends funcArg>(inp: funcArg):funcOut<Type> { let key:keyof Type & string const res:funcOut<Type> = {} as funcOut<Type> for(key of Object.keys(inp)) { res[key+'Raw'] = inp[key] // TS错误 res[key+'Result'] = new Array(inp[key]) // TS错误 } return res } const input: input= {name:'test'} const outcome = X<input>(input) console.log(outcome) // 运行结果正确:{nameRaw: "test", nameResult: ["test"]}
错误原因与解决方案
错误原因
Object.keys(inp)返回string[]类型,即便将key标注为keyof Type & string,TypeScript仍无法推断key+'Raw'或key+'Result'是funcOut<Type>的有效索引键。- 泛型
Type的约束funcArg是字符串索引签名,但funcOut<Type>的键由Type的键拼接而来,TS无法在循环中建立字符串拼接后的键与目标类型的关联。
解决方案
通过类型断言或辅助函数告知TS拼接后的键是目标类型的有效索引,以下是优化后的代码:
type funcArg = { [index: string]: any } type input = funcArg & { name: string } type funcOut<Type> = { [Property in keyof Type as `${Property & string}Raw`]: Type[Property] } & { [Property in keyof Type as `${Property & string}Result`]: Array<Type[Property]> } function X<Type extends funcArg>(inp: Type): funcOut<Type> { const res = {} as funcOut<Type> for (const [key, value] of Object.entries(inp)) { // 将拼接后的键断言为funcOut<Type>的有效索引 res[`${key}Raw` as keyof funcOut<Type>] = value res[`${key}Result` as keyof funcOut<Type>] = [value] } return res } const input: input = { name: 'test' } const outcome = X(input) console.log(outcome) // {nameRaw: "test", nameResult: ["test"]}
关键优化点
- 将
inp的类型改为Type而非funcArg,让TS更精准地推断泛型类型。 - 使用
Object.entries替代Object.keys,直接获取键值对,减少类型歧义。 - 通过类型断言明确拼接后的键属于
funcOut<Type>的索引集合,消除类型错误。
内容的提问来源于stack exchange,提问作者user1345112
相关产品推荐
相关产品推荐

