簡體   English   中英

根據推斷的參數類型區分 function 返回類型

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM