1.TypeScript4.3.2の破壊的変更について
4.3.1-rcから破壊的変更が混入
TypeScript4.3.2、正確には4.3.1-rcから致命的な破壊的変更がマージされてしまいました。細かい経緯は以下を参照してください
Consider inferences between mapped type templates lower priority
MappedTypesの優先順位の変更
これによってMappedTypesから型を抽出する際に、優先順位の変更が起こりました
2.発生内容について語る前の、前提条件の説明
変換前の基本の型
RestAPIの型をTypeScript上で展開するときによく使うのですが、以下のような基本型があったとします。keyによってデータの種類が異なっています。
{
200: { value: number };
500: { error: string };
}
変換後の型
これを扱いやすい形にします。
{
code: 200;
body: {value: number;};
} | {
code: 500;
body: {error: string;};
}
変換後に出来るようになること
const func = (
p: {
code: 200;
body: { value: number };
} | {
code: 500;
body: { error: string };
}
) => {
if (p.code === 200) { //型が確定
console.log(p.body.value);
} else {
console.log(p.body.error);
}
};
上記のようにcodeの値によって、自動的に付随するデータの型を決定することが出来ます。
私はopenapi-typescriptというパッケージで生成した型情報を、以上のような形で変換して使っています。
3.変換処理をTypeScriptで書いた場合と、バージョン間の差異について
型変換を行うためのコード
この変換を行うためにはMappedTypesで型情報を生成し、生成した情報を部分的に切り出す流れになります。
type Base = { 200: { value: number }; 500: { error: string } };
type ConvertType<T = Base> = {
[P in keyof T]: {
code: P;
body: T[P];
};
} extends {
[P in keyof T]: infer R;
}
? R
: never;
バージョンによる差異
4.3.1-rc未満
{
code: 200;
body: {
value: number;
};
} | {
code: 500;
body: {
error: string;
};
}
4.3.1-rc以降
{
code: keyof Base;
body: {
value: number;
} | {
error: string;
};
}
4.3.1-rc以降では型の抽出に失敗
inferで抽出した型情報として、本来オブジェクト単位でunionにするべきところが、オブジェクト内のプロパティ部分で統合されています。これでは使い物になりません。
4.致命的な破壊行為に対抗する
なし崩し的にこの変更がマージされていますが、まともにTypeScriptの機能を利用している側からするとたまったものではありません。現状でこの優先順位の変更がTypeScript上で解決される気配が全く見えません。ということでこの優先順位の変更に対する対策を自らが講じる必要があります。
対策コード
type Base = { 200: { value: number }; 500: { error: string } };
type ConvertType<T = Base> = {
[P in keyof T]: {
code: P;
body: T[P];
};
} extends {
// [P in keyof T]: infer R;
[P in any]: unknown extends unknown ? infer R : never
}
? R
: never;
試行錯誤したあげく、苦肉の策で導き出したコードです。inferで抽出する前に意味の無い型変換を行い、強制的に優先順位を変更するという技です。これで全バージョンで共通して目的の型を抽出することが出来ます。
ちなみにこのコード、eslintがRは未使用だという警告を発します。どうやら理解不能コード過ぎて解釈しきれないようです。
TypeScriptの恐怖
TypeScriptは頻繁に新機能が導入されています。おかげでとても便利になっている反面、時々ヤバい変更が投げ込まれるようです。複雑な型情報を処理しているとコードがパズル化していくのですが、TypeScript上でそのパズルのルールが変更されたとき、空から恐怖の大王が来るのです。