2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

TypeScript4.3.2のMappedTypes優先度変更の破壊的変更を回避する

Last updated at Posted at 2021-05-30

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上でそのパズルのルールが変更されたとき、空から恐怖の大王が来るのです。

2
1
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?