Typescript中的協變、逆變、雙向協變
阿新 • • 發佈:2021-06-29
協變(Covariant)、逆變(Contravariant)、雙向協變(Bivariant)並非Typescript所特有,其他結構化語言諸如c#、java等也都擁有該特性。
怎麼理解這個概念呢? 先說說集合、超集、子集(set, superset, subset)
下圖中有兩個集合:脊索動物、哺乳動物。 哺乳動物一定是脊索動物,反之則不一定。 因此我們說脊索動物是哺乳動物的超集,哺乳動物是脊索動物的子集。
哺乳動物一定具有脊索動物的特性,反之則不一定。
協變
協變是指:子集能賦值給其超集。
class Chordate { hasSpine(): boolean { return true; } } class Mammal extends Chordate { canBreastFeed(): boolean { return true; } } function foo(animal: Chordate){ animal.hasSpine(); } foo(new Chordate()); foo(new Mammal());
以上程式碼證明了Typescript支援協變,Mammal是Chordate的子集,方法foo接受引數型別為Chordate,而Mammal例項也能賦值給Chordate引數。
逆變
逆變(Contravariance)與雙變(Bivariance)只針對函式有效。 --strictFunctionTypes 開啟時只支援逆變,關閉時支援雙變。
class Chordate { hasSpine(): boolean { return true; } } class Mammal extends Chordate { canBreastFeed(): boolean { return true; } } declare let f1: (x: Chordate) => void; declare let f2: (x: Mammal) => void; f2=f1; f1=f2; //Error: Mammal is incompatible with Chordate
協變比較好理解,為什麼函式賦值,只能支援逆變(預設),而不支援協變呢? 請看以下程式碼示例
class Animal { doAnimalThing(): void { console.log("I am a Animal!") } } class Dog extends Animal { doDogThing(): void { console.log("I am a Dog!") } } class Cat extends Animal { doCatThing(): void { console.log("I am a Cat!") } } function makeAnimalAction(animalAction: (animal: Animal) => void) : void { let cat: Cat = new Cat() animalAction(cat) } function dogAction(dog: Dog) { dog.doDogThing() } makeAnimalAction(dogAction) // TS Error at compilation, since we are trying to use `doDogThing()` to a `Cat`
上述程式碼說明了如果函式賦值支援協變的話,有可能會導致bug
參考:
https://codethrasher.com/post/2019-08-28-type-variance-and-typescript/
https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-6.html
https://dev.to/codeozz/how-i-understand-covariance-contravariance-in-typescript-2766