![](/img/trans.png)
[英]Narrowing type based on inferred type from a function parameter used as index for yet another type
[英]Discriminate function return type based on inferred parameter type
我試圖准確地表達一個簡單的 function 的類型,它檢查一些條件並返回一個值,否則返回一個默認值。 由於默認值在大多數情況下是常量,因此返回預期值類型和默認值類型的聯合是有意義的,因此可以在調用站點適當地縮小范圍。
首先,function 和預期用例。
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);
第一個參數的類型始終相同,實際上並不影響區分,只是為了簡單的代碼示例而添加的。 Output 類型始終包含字符串。 僅當 output 類型包含 null 和 undefined 時,該區分才會生效,具體取決於調用站點的默認參數類型。
使用指定的 function 類型,編譯器顯然會抱怨案例 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 可分配給 T1,但它不能作為重載工作。
T2 簡潔,滿足所有用例,但 f 仍然不能分配給它。 此外,T2 允許錯誤的用例,例如const incorrect: number = f('', <any>1);
,因為extends
太寬容了。
有沒有辦法創建一個類型來描述與上面的接口相同的重載? 並聲明相同的 function 以便可以分配給接口或類型?
以下是如何使可區分類型與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中看到運行測試的結果
我認為您的T2
可能足夠接近實用目的,但請注意f('')
返回類型string | null | undefined
string | null | undefined
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 參數和元組類型來捕獲這樣一個事實,即當省略defaultValue
時,您希望泛型類型參數undefined
而不是string | null
string | null
。 g<T extends A>(x?: T): void
會在不帶參數的情況下調用g()
時為T
推斷A
,但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
類型值的人說“買者自負”,因為它經常以陰險的方式污染類型系統。 如果您真的想這樣做,有一些方法可以any
到,盡管 function 打字變得比我認為值得的更令人討厭:
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
耶? 誰知道。
好的,希望有幫助; 祝你好運!
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.