TypeScript Tip #3

Apr 9, 2022

7 mins read

背景

今回もmattoさんのコードリーディングをしていく。
TypeScript Tip #3


概要

今回はTypeScriptのユーティリティをまとめたライブラリts-toolbeltについて説明していく。
注意点としては、TypeScriptバージョンが4.1以上である必要がある。 数多くのユーティリティが提供されているため、今回はMattさんの解説に絞ってまとめていく。
ts-toolbelt


本題

使い方

ライブラリの中から使用したいユーティリティをimportして使用する。

import { String, Union } from 'ts-toolbelt';

今回はURLの操作をする時に便利なユーティリティをまとめていく。

サンプルコード

import { String, Union } from 'ts-toolbelt';

const query = '/home?a=foo&b=wow'

type Query = typeof query
//  type Query = "/home?a=foo&b=wow"

type SecondQueryPart = String.Split<Query, '?'>[1]
//  type SecondQueryPart = "a=foo&b=wow"

type QueryElements = String.Split<SecondQueryPart, '&'>
//  type QueryElements = ["a=foo", "b=wow"]

type QueryParams = {
    [ QueryElement in QueryElements[number] ]: {
        [ key in String.Split<QueryElement, '='>[0]]: String.Split<QueryElement, '='>[1]
    }
}[QueryElements[number]]
//  type QueryParams = {
//    a: "foo";
//  } | {
//    b: "wow";
//  }

const obj: Union.Merge<QueryParams> = {
    a: 'foo',
    b: 'wow'
}
//  const obj: {
//    a: "foo";
//    b: "wow";
//  }
  1. type SecondQueryPart = String.Split<Query, "?">;でリテラル型を?で区切りタプル型を作成する。 よって、type SecondQueryPart = ["/home", "a=foo&b=wow"]になる。その後、ルックアップ型で要素を取得している。
    type SecondQueryPart = String.Split<Query, '?'>[1]type SecondQueryPart = "a=foo&b=wow"の型を取得することができる。
    TypeScript のルックアップ型と keyof キーワード

  2. type QueryElements = String.Split<SecondQueryPart, "&">;は上記と同じように&で区切りタプル型を作成している。
    そのため、type QueryElements = ["a=foo", "b=wow"]になる。

  3. QueryParams型のインデックスシグネチャをみていく。
    QueryElement in QueryElements[number]QueryElements[number]は、以下の様になる。

QueryElements[number]
🔽
["a=foo", "b=wow"][number]
🔽
"a=foo" | "b=wow"

よって、QueryElement in QueryElements[number]QueryElement in "a=foo" | "b=wow"でマップ型が作成される。 4. Key in String.Split<QueryElement, "=">[0]String.Split<QueryElement, "=">[0]は以下になる。
a=fooの場合

String.Split<'a=foo', "=">[0]
🔽
"a"

上記と同じ様にString.Split<QueryElement,"=">[1]も時も以下の様になる。

String.Split<QueryElement,"=">[1]

🔽

"foo"
  1. ここまでを見ると最後のルックアップ型を考慮せずに記載すると以下になる
type QueryParams = {
    [QueryElement in QueryElements[number]]: {
        [Key in String.Split<QueryElement, "=">[0]]: String.Split<QueryElement,"=">[1];
    };
}

🔽

type QueryParams = {
    "a=foo": {
        a: "foo";
    };
    "b=wow": {
        b: "wow";
    };
}
  1. 上記のコードに最後のルックアップ型を追加すると、指定したキーのバリューを取得することができる。
type QueryParams = {
  [QueryElement in QueryElements[number]]: {
    [Key in String.Split<QueryElement, "=">[0]]: String.Split<QueryElement,"=">[1];
  };
}[QueryElements[number]];

🔽

type QueryParams = {
    "a=foo": {
        a: "foo";
    };
    "b=wow": {
        b: "wow";
    };
}["a=foo" | "b=wow"];

🔽

type QueryParams = {
    a: "foo";
} | {
    b: "wow";
}
  1. 最後にUnion.Merge<T>でユニオン型の型をインターセクション型に定義することができる。
Union.Merge<QueryParams>
    
🔽

Union.Merge<
        {
            a: "foo";
        } | {
            b: "wow";
        }>
    
🔽

 {
     a: "foo";
     b: "wow";
 }

まとめ

ts-toolbeltの存在を初めて知ったので、非常に勉強になった。比較的バージョンを新しくする必要があるので、 使用できる環境があれば、積極的に公式のドキュメントを参考にしていきたい。


参考文献

TypeScript Tip #3
ts-toolbelt
TypeScript のルックアップ型と keyof キーワード