如何编写TS函数:接收多类型输入,返回string|null/undefined?
我需要编写一个TypeScript函数,它接受string|number|null|undefined类型的参数,但仅返回string或对应的null|undefined类型。
我尝试实现了toLocaleString1函数,但它会出现编译错误,只有通过大量类型断言(比如toLocaleString2的写法)才能正常编译。但我认为已经做了null和undefined的检查,没必要用这些断言。
我的代码逻辑分析
- 空值检查:
if (value === null || value === undefined) return value;
这一步之后,value的类型应该被收窄为null|undefined,返回值类型应该和输入泛型T一致。比如传入string|null类型的变量,返回类型应该是string|null——如果输入是string则返回格式化后的string,如果是null则直接返回null。
我原本用这个条件类型定义返回值:T extends (string|number) ? string : never | Exclude<T, number>
- 解析数字:
const nr = parseFloat(value as string);
这里必须加类型断言,因为parseFloat对null、undefined和非数字值只会返回NaN,不过其实前面的空值检查在技术层面并非必需。
- 处理非有效数字:
if(!isFinite(nr)) return value.toString();
这一步时value已经是string|number类型,我们将其转为string返回(如果是number类型的话)。
- 返回本地化字符串:
return nr.toLocaleString(locale);
这里同样返回string类型,此时输入必然是string|number类型,理论上应该可以正常运行。
核心疑问
toLocaleString1的问题出在哪里?它为什么不能正常编译?我测试了各种输入的类型推断结果,都符合预期,两个函数运行时功能完全正常,但第一个就是会报编译错误。
目标重载行为
我希望函数支持以下重载:
string => stringstring | null => string | nullstring | null | undefined => string | null | undefinedstring | undefined => string | undefinedstring | number => stringstring | number | null => string | nullstring | number | null | undefined => string | null | undefinedstring | number | undefined => string | undefinednumber | null => string | nullnumber | null | undefined => string | null | undefinednumber | undefined => string | undefinednull | undefined => null | undefinednull => nullundefined => undefined
完整代码示例
// 编译报错: function toLocaleString1<T extends number|string|null|undefined>(value: T, locale?: string): T extends (string|number) ? string : never | Exclude<T, number> { if (value === null || value === undefined) return value; const nr = parseFloat(value as string); if(!isFinite(nr)) return value.toString(); return nr.toLocaleString(locale); } // 功能相同但使用了大量类型断言: function toLocaleString2<T extends number|string|null|undefined, R extends (T extends (string|number) ? string : never | Exclude<T, number>) >(value: T, locale?: string): R { const nr = parseFloat(value as string); if(!isFinite(nr)) return `${value}` as unknown as R; return nr.toLocaleString(locale) as unknown as R; } const TYPES = ['string', 'number', 'null', 'undefined', 'NaN'] as const; function value(type: (typeof TYPES)[number] ) : string | number | null | undefined { switch(type) { case 'string': return "12345.678"; case 'number': return 98765.4321; case 'null': return null; case 'undefined': return undefined; case 'NaN': return NaN; } } console.log('-------------') for (const type of TYPES) { const input = value(type); const formatted1 = toLocaleString1(input, 'de'); const formatted2 = toLocaleString2(input, 'fr'); console.log(type, input, formatted1, formatted2); }
内容的提问来源于stack exchange,提问作者Claude Martin

