簡體   English   中英

如何在 TypeScript 中鏈接/加入/關聯兩個函數參數的類型?

[英]How do I link/join/relate the types of two function parameters in TypeScript?

基於關於鏈接類字段的類似問題,我如何讓 TypeScript 識別函數的一個參數的值(其值在運行時確定)限制了另一個參數的類型?

示例代碼( 操場):

class Cat { purr() {/*...*/} }
class Dog { bark() {/*...*/} }
interface TypeMap {
    cat: Cat;
    dog: Dog;
}
const makeSound = function<C extends keyof TypeMap>(
    commonName: C,
    animal: TypeMap[C],
) {
    if(commonName === 'cat') {
        //Error: this.animal is of type 'Cat | Dog',
        //not narrowed to Cat as hoped.
        return animal.purr();
    } else if (commonName === 'dog') {
        //Error: this.animal is of type 'Cat | Dog',
        //not narrowed to Dog as hoped.
        return animal.bark();
    }
}
//The intended type-checking is being done in calling contexts:
const caller = function(cat: Cat, dog: Dog) {
    //Error on next line as expected:
    //Argument of type '"dog"' is not assignable
    //to parameter of type '"cat"'.ts(2345)
    makeSound<'cat'>('dog', dog);
    makeSound<'cat'>('cat', dog); //similar error, as expected
    makeSound<'cat'>('cat', cat); //works
    makeSound('cat', cat); //desired style; cat vs. dog not hard-codable
}

TS 文檔指出“您可以聲明受另一個類型參數約束的類型參數。” 但是給出的示例非常簡單,並且在泛化到像這個稍微復雜的示例的能力方面似乎很脆弱。

您正在針對個別情況手動檢查commonName ,如果makeSound()的主體是generic ,TypeScript 確實沒有很好的類型檢查支持。 即使commonName是泛型類型C ,檢查commonName只會縮小commonName本身的類型,它不會進一步限制C 所以不幸的是,即使編譯器知道commonName"cat" ,它也不知道C"cat" 依舊可以是"cat" | "dog" "cat" | "dog" ,因此擔心(誠然不太可能)這樣的電話的可能性

makeSound(
  Math.random() < 0.99 ? "cat" : "dog", 
  Math.random() < 0.99 ? dog : cat
); // no error?!

我們實際上沒有建立我們想要建立的鏈接,即使它通常已經足夠好(我的意思是,真的,誰將交叉相關的聯合傳遞給這樣的函數?),編譯器不願意對主體進行類型檢查。

這里有各種要求改進的功能請求。 例如, microsoft/TypeScript#27808希望可以說C extends oneof keyof TypeMap以便拒絕 TypeMap 的完整聯合類型keyof TypeMap 現在雖然什么都沒有實現,如果我們想編寫你的代碼,我們將需要重構(或者用類型斷言來填充你的代碼以抑制錯誤......但讓我們研究重構。)


因為makeSound()的返回類型不依賴於C ,所以您可以將調用簽名重構為強制執行實際約束的非泛型版本。 本質上,參數tuple , [commonName, animal]形成了一個可區分的聯合,其中索引0處的屬性是判別式,檢查它會縮小索引1處的屬性類型。 你可以像這樣計算這個聯合類型:

type MakeSoundParam = { [K in keyof TypeMap]:
    [commonName: K, animal: TypeMap[K]]
}[keyof TypeMap];

/* type MakeSoundParam = 
  [commonName: "cat", animal: Cat] | 
  [commonName: "dog", animal: Dog] */

TypeScript 支持對解構的可區分聯合進行控制流分析,這意味着您可以將函數重寫為:

const makeSound = function (...[commonName, animal]: MakeSoundParam) {
    if (commonName === 'cat') {
        return animal.purr(); // okay
    } else if (commonName === 'dog') {
        return animal.bark(); // okay
    }
}

編譯沒有錯誤。 並且只是為了確保makeSound()從調用方的角度仍然正常運行:

const caller = function (cat: Cat, dog: Dog) {
    makeSound('cat', cat); // ok
    makeSound('dog', dog); // ok
    makeSound('cat', dog); // error
    makeSound('dog', cat); // error
    makeSound(
      Math.random() < 0.99 ? "cat" : "dog", 
      Math.random() < 0.99 ? dog : cat
 ); // error
}

看起來不錯,連互相關調用都被拒絕了。

Playground 代碼鏈接

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

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