这是我的函数的简化复制:
const nullify = <T extends string | undefined>(
value?: T,
): T extends string ? string : null => {
if (value === undefined) return null as any;
return value as any;
};
我对这些函数类型的期望是将缺少value
视为undefined
,但TypeScript不允许这样做,而是返回一个联合string | null
。从这个函数得到null
的唯一方法是显式地传递undefined
。
const result1 = nullify('string'); // result1 is string, as expected
const result2 = nullify(undefined); // result2 is null, as expected
const result3 = nullify(); // result3 is (string | null), but should be just null
有一个习惯的方法来处理这个吗?
问题是,当您不传递value
参数时,编译器无法从中推断T
的值,并且推断失败。当推理失败时,它通常会回到它的约束,在您的例子中是string | undefined
。如果您想更改它的返回值,您可以使用=
语法设置一个通用参数default:
const nullify = <T extends string | undefined = undefined>(
// default generic ----> ^^^^^^^^^^^
value?: T,
): T extends string ? string : null => {
if (value === undefined) return null as any;
return value as any;
};
只有当你把value
去掉时,你才会看到不同:
const result1 = nullify('string'); // string, as expected
const result2 = nullify(undefined); // null, as expected
const result3 = nullify(); // null, as expected 👍
万岁!
有一个缺点,从技术上讲,调用者可以指定他们想要的T
的任何值。所以没有什么能阻止一个特别精神错乱的调用者这样做:
const oops = nullify<string>(); // string, oops
// ---------------> ^^^^^^^^ <--- manually specify string
由于string
是为T
手工指定的,value
参数是可选的,所以不会出现编译错误。现在的情况是,编译器错误地认为null
的值是string
类型的。
这可能不太可能发生,所以你可以不管它,不要担心它。实际上,TypeScript在很多地方都是不健全的,所以你不会在这里把事情弄得更糟。
但是,如果你关心这些事情,那么你需要重构。一种方法是使泛型类型参数对应于rest参数的元组类型:
const nullify = <T extends [string] | [undefined?]>(
...[value]: T
): T extends [string] ? string : null => {
if (value === undefined) return null as any;
return value as any;
};
const result1 = nullify('string'); // string, as expected
const result2 = nullify(undefined); // null, as expected
const result3 = nullify(); // null, as expected
现在您不能轻易传递错误的内容,因为value
缺失或undefined
唯一可接受的情况是T
扩展[undefined?]
:
const okayNow = nullify<[string]>(); // error, as expected
做这类事情的传统方法是使用重载而不是泛型(或与泛型一起使用),但我不会深入讨论这个问题,而是向感兴趣的各方指出如何为泛型参数提供默认值?查看更多信息。
Playground链接到代码
尝试检查value是否为字符串。
const nullify = <T extends string | undefined>(
value?: T,
): T extends string ? string : null => {
if (typeof value === 'string') return value as any;
return null as any;
};