TypeScript中如何不使用as/any实现字典值类型的转换功能?
结论
分两种场景实现:如果不需要支持Foo的子类型扩展,可以完全不使用类型断言实现;如果需要保留泛型灵活性,目前TypeScript类型系统无法自动完成泛型映射类型的推断,需要结合运行时校验加安全的类型断言实现。
方案1:无断言实现(限定输入为固定Foo类型)
如果你的输入只会是Foo类型,不需要兼容额外扩展的子类型,可以直接去掉泛型约束,TypeScript可以正常推断返回值类型:
type Tuplet1<T> = [T] type Tuplet2<T> = [T,T] type Foo = { a: Tuplet1<string> b: Tuplet2<string> } type Magic<T, N, M> = { [key in keyof T]: T[key] extends Tuplet1<N> ? Tuplet1<M>[] : T[key] extends Tuplet2<N> ? Tuplet2<M>[] : unknown } // 直接限定输入为Foo类型,不需要泛型 function convert(input: Foo): Magic<Foo, string, number> { return { // 转换a字段:每个长度为1的字符串元组转成数字元组 a: input.a.map(([str]) => [Number(str)]), // 转换b字段:每个长度为2的字符串元组转成数字元组 b: input.b.map(([str1, str2]) => [Number(str1), Number(str2)]) } } // 测试效果和原来完全一致 const foo:Foo = { a:['s'], b:['s','s'] } const dynamicMagicFoo = convert(foo) dynamicMagicFoo.a // Tuplet1<number>[] dynamicMagicFoo.b // Tuplet2<number>[]
方案2:保留泛型灵活性(结合运行时校验的安全断言)
如果你需要支持Foo的子类型(比如给Foo加额外字段),可以添加类型守卫做运行时校验,再使用类型断言,既保证类型安全又保留泛型能力:
// 类型守卫:判断是不是长度为1的元组 function isTuplet1<T>(val: unknown): val is Tuplet1<T> { return Array.isArray(val) && val.length === 1 } // 类型守卫:判断是不是长度为2的元组 function isTuplet2<T>(val: unknown): val is Tuplet2<T> { return Array.isArray(val) && val.length === 2 } function convert<T extends Foo>(input: T): Magic<T, string, number> { const result: Record<string, unknown> = {} // 遍历所有字段处理 for (const key in input) { const value = input[key] if (isTuplet1<string>(value)) { result[key] = value.map(item => [Number(item)]) } else if (isTuplet2<string>(value)) { result[key] = value.map(([item1, item2]) => [Number(item1), Number(item2)]) } else { // 其他字段按Magic定义返回unknown result[key] = undefined } } // 这里的断言是安全的,因为运行时逻辑已经保证返回结构符合Magic类型定义 return result as Magic<T, string, number> }
原理解释
TypeScript目前无法在编译期对泛型参数的映射类型做自动推断:因为编译器不知道泛型T的具体结构,无法验证你构造的对象和Magic<T, string, number>完全匹配,所以保留泛型的前提下断言不可避免,但可以通过运行时校验保证逻辑的安全性,避免类型断言带来的隐患。
内容的提问来源于stack exchange,提问作者pailhead
相关产品推荐
相关产品推荐

