KENTEM TechBlog

建設業のDXを実現するKENTEMの技術ブログです。

型でもテンプレートリテラル?type Y = `${X}`という書き方

UnsplashJoanna Kosinskaが撮影した写真

こんにちは。ReactエンジニアのT.C.です。

TypeScriptの型を知らなくても動作やパフォーマンスには直接影響しないので、AIなどが便利に活用できるようになった現在でも意識して学ばないと触れる機会が少ないと思います。

Mapped TypesやIndexed Access TypesなどTypeScriptにはやや高度な型定義の方法がありますが、Template Literal Typesという書き方に最近気づいたので、ご紹介します。

詳しいことや具体的な実装は公式ページやAIにお任せしますので、本記事では「こんなこともできるよ」と知っていただけたら嬉しいです。

Template Literal Typesでできること

JavaScriptにはテンプレートリテラル( ${変数} )という書き方があり、これを活用することで静的な文字列と変数を組み合わせた経験のある方は多いと思います。

今回ご紹介したいのはJavaScriptのロジックで利用するテンプレートリテラルではなく、TypeScriptの型で使えるテンプレートリテラル型(Template Literal Types)です。

具体例

実際のコードを見た方が早いと思いますので、4つ例をお見せします。

最もシンプルな例

以下のように特定の文字を含むリテラル型を作ることができます。

注目していただきたいのはtypeで定義しているところにテンプレートリテラルが書いてあるところです。

type A = "a"
type AX = `${A}-x`
// type AX = "a-x"

ユニオン型を使った例

ユニオン型を使うことでユニオン型がそれぞれ展開されます。

type AB = "a" | "b"
type ABX = `${AB}-x`
// type ABX = "a-x" | "b-x"

ユニオン型同士の例

こちらはユニオン型を組み合わせて全てのパターンを網羅したものを簡単に作成できる例です。

type AB = "a" | "b"
type XY = "x" | "y"
type ABXY = `${AB}-${XY}`
// type ABXY = "a-x" | "a-y" | "b-x" | "b-y"

ジェネリクスと組み合わせた例

ジェネリクスを使うこともできるので、後ほど紹介しますがinferと組み合わせることでより多くのことができるようになります。

type ABC = "a" | "b" | "c"
type XYZ = "x" | "y" | "z"
type Hoge<T extends string, U extends string> = `${T}-${U}`
type Fuga = Hoge<ABC, XYZ>
// type Fuga = "a-x" | "a-y" | "a-z" | "b-x" | "b-y" | "b-z" | "c-x" | "c-y" | "c-z"

更に便利に使うための型

Template Literal Typesを更に便利にできる4つの型(Intrinsic String Manipulation Types)も簡単にご紹介します。

Uppercase

リテラル型を全て大文字に変換します。

type Hoge = "Hello, world"
type Fuga = Uppercase<Hoge>
// type Fuga = "HELLO, WORLD"

Lowercase

リテラル型を全て小文字に変換します。

type Hoge = "Hello, world"
type Fuga = Lowercase<Hoge>
// type Fuga = "hello, world"

Capitalize

リテラル型の先頭の文字を大文字に変換します。

type Hoge = "hello, world"
type Fuga = Capitalize<Hoge>
// type Fuga = "Hello, world"

Uncapitalize

リテラル型の先頭の文字を小文字に変換します。

type Hoge = "HELLO, WORLD"
type Fuga = Uncapitalize<Hoge>
// type Fuga = "hELLO, WORLD"

Inferを組み合わせた実例

Type challengesにも登場している2つの例を出して、inferを組み合わせることで命名規則を変更することができるところをお見せします。

以下の例はどちらもinferと再帰処理を活用することで一文字ずつチェックして置き換えるということを実現しています。

KebabCase

https://github.com/type-challenges/type-challenges/blob/main/questions/00612-medium-kebabcase/README.ja.md

type KebabCase<S extends string> = S extends `${infer S1}${infer S2}`
  ? S2 extends Uncapitalize<S2>
  ? `${Uncapitalize<S1>}${KebabCase<S2>}`
  : `${Uncapitalize<S1>}-${KebabCase<S2>}`
  : S;

type FooBarBaz = KebabCase<"FooBarBaz">;
// type FooBarBaz = "foo-bar-baz"

CamelCase

https://github.com/type-challenges/type-challenges/blob/main/questions/00114-hard-camelcase/README.ja.md

type CamelCase<S extends string> = S extends Lowercase<S>
  ? S extends `${infer S1}_${infer S2}${infer S3}`
    ? `${S1}${Uppercase<S2>}${CamelCase<S3>}`
    : S
  : CamelCase<Lowercase<S>>;

type FooBarBaz = CamelCase<"foo_bar_baz">
// type FooBarBaz = "fooBarBaz"

まとめ

APIから受け取ったPascalCaseやsnake_caseのキーを持つJSONに対して、キーをcamelCaseに変換する型関数のためにTemplate Literal Typesが使えそうだと考えています。

他にもCSSクラス名をケバブケースで管理している場合に変数名をケバブケースに変換する型関数などにも使えるかもしれません。

個人的にはとても面白い機能だなと思いましたが、一方でやや高度な型だと思いますのでチームのスキルレベルと相談しながら利用を検討していただければと思います。

おわりに

KENTEMでは、様々な拠点でエンジニアを大募集しています! 建設×ITにご興味頂いた方は、是非下記のリンクからご応募ください。 recruit.kentem.jp career.kentem.jp