我正试图表达一个简单函数的类型,该函数检查某些条件并返回一个值,否则返回一个默认值。由于默认值在大多数情况下是一个常数,因此返回预期值和默认值类型的并集是有意义的,这样可以在调用站点适当缩小范围。
首先,介绍功能和预期用例。
const f = (
value: unknown, defaultValue?: string | null | undefined
): string | null | undefined => {
if (typeof value === 'string') {
return value;
}
return defaultValue;
};
const s1: string = f('', 's');
const s2: string | null = f('', null);
const s3: string | undefined = f('', undefined);
const s4: string | undefined = f('');
const x: string | null | undefined = '';
const s5: string | null | undefined = f('', x);
第一个参数的类型始终是相同的,它实际上并不影响区分,仅出于简单的代码示例添加。输出类型始终包含字符串。仅当输出类型包含null和undefined时(根据调用站点的默认参数的类型),区分才应生效。
使用指定的函数类型,编译器显然会抱怨情况1-4。
现在,我进行了各种尝试来定义区分类型。
interface I {
(value: unknown, defaultValue: string): string;
(value: unknown, defaultValue: null): string | null;
(value: unknown, defaultValue?: undefined): string | undefined;
// (value: unknown, defaultValue?: string | null | undefined): string | null | undefined;
}
type T1 = ((value: unknown, defaultValue: string) => string) |
((value: unknown, defaultValue: null) => string | null) |
((value: unknown, defaultValue?: undefined) => string | undefined) |
((value: unknown, defaultValue: string | null | undefined) => string | null | undefined)
type T2 = <U extends string | null | undefined> (
value: unknown, defaultValue?: U
) => string | U;
具有重载的接口I可以满足所有用例,但是即使我取消对最后一个与f的类型完全匹配的重载的注释,也无法为f分配f。重载是冗长的。
f可分配给T1,但不能作为重载使用。
T2很简洁,可以满足所有用例,但是f不能分配给它。此外,T2允许使用错误的用例,例如const incorrect: number = f('', <any>1);
,因为extends
太宽容了。
是否有一种方法可以创建一个描述与上面接口相同的重载的类型?并声明相同的功能,以便可以将其分配给接口或类型?
答案 0 :(得分:1)
这里是如何使可区分的类型与f
一起使用:
interface IDiscriminate {
(value: string, defaultValue?: unknown): string;
<T>(value: unknown, defaultValue?: T): T;
}
const f: IDiscriminate = <T>(value: unknown, defaultValue?: T) => {
if (typeof value === 'string') {
return value;
}
return defaultValue;
};
const s1: string = f('', 's');
const s2: string = f('', null);
const s3: string = f('', undefined);
const s4: string = f('');
const x: string = 'x';
const s5: string = f('', x);
const s6: string = f(1, x);
const s7: null = f(1, null);
const s8: undefined = f(true);
const s9: number = f(1, 1);
//const incorrect: number = f('', <any>1); // Fails to compile
console.log(`s1 === '': ${s1 === ''}`); // true
console.log(`s2 === '': ${s2 === ''}`); // true
console.log(`s3 === '': ${s3 === ''}`); // true
console.log(`s4 === '': ${s4 === ''}`); // true
console.log(`s5 === '': ${s5 === ''}`); // true
console.log(`s6 === 'x': ${s6 === 'x'}`); // true
console.log(`s7 === null: ${s7 === null}`); // true
console.log(`s8 === undefined: ${s8 === undefined}`); // true
console.log(`s9 === 1: ${s9 === 1}`); // true
您可以在Playground
中查看运行测试的结果答案 1 :(得分:1)
我认为您的T2
可能出于实际目的足够接近,但是请注意,f('')
返回的类型为string | null | undefined
。如果我想尝试更精确地获得它,则可以执行以下操作:
const f = <T extends [(string | null)?]>(
value: unknown, ...[defaultValue]: T
): string | T[0] => {
if (typeof value === 'string') {
return value;
}
return defaultValue;
};
这使用rest parameters and tuple types来捕获以下事实:省略defaultValue
时,您希望泛型类型参数为undefined
而不是string | null
。这是一种类型推断的怪癖,当您不带参数调用g<T extends A>(x?: T): void
时,A
会为T
推断g()
,但是g<T extends [A?]>(...[x]: T): void
会推断[]
,因此T[0]
是undefined
。
您可以看到所有用例都可以正常工作:
const s1 = f('', 's'); // string
const s2 = f('', null); // string | null
const s3 = f('', undefined); // string | undefined
const s4 = f(''); // string | undefined
const x = ["", null, void 0][Math.floor(Math.random() * 3)]; // string | null | undefined
const s5 = f('', x); // string | null | undefined
所以也许您想要的不是T2
// typeof f
type T3 = <T extends [(string | null | undefined)?]>(
value: unknown, ...[defaultValue]: T
) => string | T[0];
您会发现此与I
兼容:
const i: I = f; // okay
关于您的说法,T2
(或T3
)会导致以下情况:
const incorrect = f('', <any>1); // any ?
我倾向于向使用any
类型的值的人说“ caveat emptor”,因为它经常以隐蔽的方式污染类型系统。如果您确实想这样做,可以使用detect any
的方法,尽管函数键入变得比我认为值得的更令人讨厌:
type IfAny<T, Y, N = T> = 0 extends (1 & T) ? Y : N;
const g = <T extends [(string | null)?]>(
value: unknown, ...[defaultValue]: T
): string | IfAny<T[0], null | undefined> => {
if (typeof value === 'string') {
return value;
}
return defaultValue as any;
};
const gs1 = g('', 's'); // string
const gs2 = g('', null); // string | null
const gs3 = g('', undefined); // string | undefined
const gs4 = g(''); // string | undefined
const gs5 = g('', x); // string | null | undefined
const betterMaybe = g('', <any>1); // string | null | undefined
是吗?谁知道。
好的,希望能有所帮助;祝你好运!