最近、これらの知識に触れたばかりで、記事にはいくつかの間違いがあるかもしれませんが、多くのアドバイスをいただければ幸いです(
TypeScript の学習において、反変、共変、双方向共変、不変の型の理解は非常に重要ですが、型の親子関係が理解できれば、これらの概念を理解することは容易になります。したがって、これらの概念を説明する前に、まず型の親子関係を学ぶ必要があります。
型の親子関係#
まず、TypeScript では、型の構造が同じであれば、親子関係があると言えます。これは Java とは異なります(Java では、extends を使用する必要があります)。
以下の例を見てみましょう:
これらの 2 つの型には継承関係があることがわかると思います。この場合、親と子のどちらがどちらかを考えてみましょう。
おそらく、Suemor が Person の親タイプであると考えるかもしれません(Person には 2 つのプロパティがあり、Suemor には 3 つのプロパティがあり、かつ Person を含んでいるため)。しかし、これは間違いです。
型システムでは、プロパティの数が多い方が子タイプです。つまり、Suemor は Person の子タイプです。
これは直感に反するかもしれませんが(私も最初は理解できませんでした)、次のように理解してみてください:A が B を拡張する場合、A は B のプロパティを拡張できるため、A のプロパティは通常、B よりも多くなります。したがって、A は子タイプです。または、次の特徴を覚えておくこともできます:子タイプは親タイプよりも具体的です。
また、共用型の親子関係を判断する場合、 'a' | 'b' と 'a' | 'b' | 'c' のどちらが具体的ですか?
'a' | 'b' の方が具体的ですので、 'a' | 'b' は 'a' | 'b' | 'c' の子タイプです。
共変#
オブジェクトでの使用#
共変は理解しやすいですし、日常的な開発でもよく使われます。例えば:
これらの 2 つの型は異なりますが、suemor を person に代入できます。つまり、子タイプを親タイプに代入できますが、逆はできません(なぜなら、person を suemor に代入できると、suemor.hobbiesを呼び出すとプログラムが壊れるからです)。
したがって、結論は次のとおりです:子タイプを親タイプに代入できる場合、それを共変と呼びます。
関数での使用#
関数でも共変を使用することができます。例えば:
ここでも、hobbies を追加しましたが、共変のため、子タイプを親タイプに代入できます。
したがって、通常のreduxでdispatchの型を宣言する際に、次のように書くことができます:
これにより、渡されるパラメータが必ずActionのサブタイプであることが制約されます。つまり、typeを持っている必要があり、他のプロパティはあってもなくても構いません。
双方向共変#
先ほどの例をもう一度見てみましょう:
suemor = personのエラーは、tsconfig.jsonでstrictFunctionTypes:falseを設定するか、strict モードを無効にすることで回避できます。この場合、親タイプを子タイプに代入でき、子タイプを親タイプに代入できるようになります。このような場合、双方向共変と呼びます。
ただし、これは明らかに問題があり、型の安全性を保証できません。したがって、通常は strict モードを有効にして、双方向共変を回避します。
不変#
不変は最も単純です。継承関係がない場合(A と B のどちらかが他方のすべてのプロパティを含んでいない場合)、不変です。したがって、非親子関係の型は、型が異なる場合にエラーになります:
逆変#
逆変は少し理解が難しいかもしれません。次の例を見てみましょう:
ここで気づくでしょう:fn1 のパラメータは fn2 のパラメータの親タイプですが、なぜ子タイプに代入できるのでしょうか?
これが逆変です。親タイプを子タイプに代入できる性質を持つ関数のパラメータは逆変です(戻り値は共変であり、つまり子タイプを親タイプに代入できます)。
なぜなのかは、fn1 = fn2が正しい場合、fn1('suemor',123)のようにしか渡せず、fn1の呼び出しでcを出力することができなくなるからです。
したがって、私は逆変が通常は関数と関数の間で使用されることが多いと感じています(関数の呼び出し時ではなく、関数と関数の間で使用されるという意味ですが、これが私の理解ですが、正しいかどうかはわかりません)。
逆変は、型の演算時によく使用されるため、もう少し難しい例を見てみましょう:

ここで、GetReturnTypeは戻り値の型を抽出するために使用されます。ここで、ReturnTypeResulltは"suemor"であるべきですが、上記のコードではneverとなってしまいます。
これは、関数のパラメータが逆変を遵守するためであり、つまり親タイプを子タイプに代入することしかできませんが、明らかにここではunknownは{name: string}の親タイプですので、逆です。したがって、unknownをstringの子タイプに変更する必要があります。つまり、unknownをanyまたはneverに変更する必要があります。正しい答えは次のとおりです:
