TypeScript 共変と反変について

Animal型とDog型があるとします。AnimalはDogのスーパークラスのような位置付けです。

type Animal = 'Dog' | 'Cat' | 'Tami'
type Dog = 'Dog'

イメージ的には、AnimalはDogを含みます。

そのため、以下のようにAnimal型にDog型を代入することはできますが、その逆はできません。

declare let myDog: Dog
declare let myAnimal: Animal

const animal: Animal = myDog //ok
const dog: Dog = myAnimal //ng

このようにAnimal型とDog型の関係と同じように動作するのが共変です。

Animal型はDog型を含む(広い範囲を指す)。なので、Animal型にDog型を代入できる。

一方で、反変というものがあり、これはAnimal型がDog型のスーパークラスなら、その逆の関係になることをいいます。

type AnimalFunc = (x: Animal) => void
type DogFunc = (x: Dog) => void

const a: AnimalFunc = (x: Dog) => console.log('dog') //ng
const d: DogFunc = (x: Animal) => console.log('animal') //ok

AnimalFunc型に対してDogFuncを入れようとするとエラーになります。(strictFunctionTypesがtrueの場合に限る)。

先ほどのイメージだと、AnimalFuncはDogFuncを含みそう(より広いイメージ)なので、なぜエラーになるのか?と疑問に思います。

しかし、対象が関数の引数だった場合は、逆の関係になります。

AnimalFuncは引数にAnimalを受け取って何か処理する関数です。

つまり、Dog、Cat、Tamiのどれがきても処理がされることを想定されています。

一方で、DogFuncは引数にDogだけを受け取ります。

そのため、AnimalFunc型を想定している変数にDogFunc を入れてしまうと、使う側としては(上記の例の場合、変数aの関数を実行しようとする時に)CatやTamiも引数に渡す可能性があるので、そのときにDogFuncでは処理がしきれない、という状況になります。

そのため、strictFunctionTypesがtrueの場合には、関数の引数は反変としてエラーにしています。