如何为递归返回同结构对象的函数编写TypeScript类型定义?
TypeScript递归处理嵌套对象的类型定义方案
问题场景
需要实现一个可处理任意嵌套对象/数组的递归函数,比如addCssSuffix:
- 传入数值(或可转为数字的字符串)时,返回带
px后缀的字符串,如addCssSuffix(50)返回"50px" - 传入嵌套对象/数组时,返回结构完全一致的新对象/数组,其中符合条件的数值都会被加上后缀,如
addCssSuffix({width:10, margin:[20, "30"]})返回{width:"10px", margin:["20px", "30px"]}
原TypeScript写法无法精准推断返回类型,比如const i = addCssSuffix(50)会被推断为宽泛类型,而非预期的string。
解决方案:递归条件类型映射
要让TypeScript正确推断输入输出的结构对应关系,需要使用递归条件类型逐个映射输入类型的每个成员:
1. 定义输入到输出的映射类型
先定义泛型类型CssSuffixified<T>,递归处理输入类型T的每个成员:
type CssSuffixified<T> = // 处理数值类型:转为带px的字符串 T extends number ? `${T}px` : // 处理可转为数字的字符串类型:转为带px的字符串 T extends `${number}` ? `${T}px` : // 处理数组:递归映射每个元素 T extends Array<infer U> ? CssSuffixified<U>[] : // 处理对象:递归映射每个属性值 T extends object ? { [K in keyof T]: CssSuffixified<T[K]> } : // 其他类型直接返回原类型 T;
2. 为函数添加精准类型注解
修改函数,用上述泛型类型约束输入和输出:
function addCssSuffix<T>(input: T, suffix = 'px'): CssSuffixified<T> { // 处理数组/对象的递归逻辑 if (input && typeof input === 'object') { if (Array.isArray(input)) { return input.map(item => addCssSuffix(item, suffix)) as CssSuffixified<T>; } return Object.fromEntries( Object.entries(input).map(([key, value]) => [ key, addCssSuffix(value, suffix) ]) ) as CssSuffixified<T>; } // 处理数值或可转为数字的字符串 if (typeof input === 'number' || (typeof input === 'string' && !isNaN(Number(input)))) { return `${input}${suffix}` as CssSuffixified<T>; } // 其他类型直接返回 return input as CssSuffixified<T>; }
3. 类型推断验证
现在测试场景下,TypeScript能精准推断返回类型:
// 推断i为"50px"字面量类型(本质为string) const i = addCssSuffix(50); // 推断obj为{ width: "10px", margin: ["20px", "30px"] } const obj = addCssSuffix({ width: 10, margin: [20, "30"] }); // 推断str为"100px" const str = addCssSuffix("100"); // 推断other为boolean(原类型不变) const other = addCssSuffix(true);
关键说明
- 递归条件类型
CssSuffixified<T>会逐层遍历输入类型的每个成员,确保输出类型和输入结构完全一致 - 使用
as CssSuffixified<T>是因为TypeScript无法自动识别递归函数的返回类型对应关系,手动断言是合理的(类型逻辑已保证正确性) - 针对数组和对象分别处理,确保返回的数组/对象结构与输入一致
内容的提问来源于stack exchange,提问作者Kasheftin
相关产品推荐
相关产品推荐

