以下はtype-challengesの「Include」で色々試していた時に気づいたことです。
まず、以下のConditional Typeと呼ばれる構文において、型変数TがUnion型の場合は各々の要素に対してConditionが適用されます。
「Union Distribution」とか「分配法則」と呼ばれるもので、先人の解説記事がたくさんあるので説明は割愛します。
type Condition<T, U> = T extends U ? T : never;
type Test = Condition<'Kars' | 'Esidisi' | 'Wamuu' | 'Santana' | 1, string> // -> 'Kars'|'Esidisi'|'Wamuu'|'Santana'
以上を前提の知識として、次のコードを見てください。
/* _____________ ここにコードを記入 _____________ */
type Includes<T extends readonly any[], U> = T[number] extends U ? true : false;
type Test2 = Includes<['Kars', 'Esidisi', 'Wamuu', 'Santana'], 'Kars'> // -> なぜかfalseになる
/* _____________ テストケース _____________ */
上記のコードにおいて型Test2を求める際、ジェネリクスIncludeのT[number]
は'Kars' | 'Esidisi' | 'Wamuu' | 'Santana'
になるので、分配法則が適用されるならばTest2はtrue | false | false | false
、つまりboolean
になるはずです。
しかし、実際はfalse
になります。
'Kars' | 'Esidisi' | 'Wamuu' | 'Santana'
は'Kars'
に代入できないのでfalseになっていると思われます。つまり、分配法則が適用されません。
解決方法はシンプルで、この場合はT[number]
を別の型変数に一度代入すれば解決します。例えば、次のコードがその一例になります。
/* _____________ ここにコードを記入 _____________ */
type Source = Readonly<['Kars', 'Esidisi', 'Wamuu', 'Santana']>[number];
type Includes<T, U> = T extends U ? true : false;
type Test3 = Includes<Source, 'Kars'> // -> boolean
/* _____________ テストケース _____________ */
※追記
コメントで気づかされましたが、別に改めて型変数を増やす必要ないですね
/* _____________ ここにコードを記入 _____________ */
type Includes<T, U> = T extends U ? true : false;
type Test2 = Includes<['Kars', 'Esidisi', 'Wamuu', 'Santana'][number], 'Kars'> // -> boolean
/* _____________ テストケース _____________ */
type-challengesをやったことがある方は分かると思いますが、上記のジェネリクスIncludesはchallengesの回答としては誤りです。
参考資料
参考というか、ほぼ回答ですね。この記事で原因が分かりました。ほんと、ありがとうございます。
参考資料では型関数で説明されていますが、Array[number]によるユニオン型でも同様の事象が起きたので、今回書いてみました。