0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Template Literal Typesについて調べてみた

Posted at

はじめに

Typescript Template Literal Types の勉強メモ。
公式のドキュメントを自分なりにまとめたものです。

Template Literal Types

一言で

既存の型から文字列リテラル型を作り出せる

構文

type World = 'world'
type Greeting = `hello ${World}`
// type Greeting = "hello world"

union や intersection と組み合わせることで、より複雑な型を作ることができる

type TLD = 'jp' | 'com'
type SLD = 'example' | 'test'
type Domain = `${SLD}.${TLD}`
// type Domain = "example.jp" | "example.com" | "test.jp" | "test.com"

EventEmitter の例

EventEmitter クラスの(on)[https://nodejs.org/docs/latest/api/events.html#events_emitter_on_eventname_listener]メソッドに近いもの

次の passedObject のプロパティの変更を検知するリスナーメソッドを定義したい時の例
次の制約を満たすような makeWatchedObject 関数型を実装する

  1. メソッド名は on
  2. on の引数には<プロパティ名>Changed と戻り値が void であるコールバック関数を受け取る
const passedObject =  makeWatchedObject({
  firstName: 'Saoirse',
  lastName: 'Ronan',
  age: 26,
})

passedObject.on('firstNameChanged', (v) => {...})

こうすると、passedObject に指定のシグネチャに沿った on メソッドが生える

type PropEventSource<Type> = {
    on(
      eventName: `${string & keyof Type}Changed`,
      callback: (newValue: any) => void
    ): void;
};

declare function makeWatchedObject<Type>(obj: Type): Type & PropEventSource<Type>;

// 推論される
passedObject.on(eventName: "firstNameChanged" | "lastNameChanged" | "ageChanged", callback: (newValue: any) => void): void

Inference with Template Literals

さらに、Template Literal Types を使って、コールバック関数の引数の型を推論することができる

type PropEventSource<Type> = {
    on<Key extends string & keyof Type>(
        eventName: `${Key}Changed`,
        callback: (newValue: Type[Key]) => void
    ): void;
};

// 推論される
passedObject.on<Key>(eventName: `${Key}Changed`, callback: (newValue: {
    firstName: string;
    lastName: string;
    age: number;
}[Key]) => void): void

Intrinsic String Manipulation Types

テンプレートリテラルを使った文字列型が ts に組み込まれて提供されている

  • Uppercase
  • Lowercase
  • Capitalize
  • Uncapitalize

余談、Uppercase を作ってみる

type _Uppercase<T extends {}> = T extends `${infer First}${infer Rest}`
  ? `${UppercaseChar<First>}${Rest}`
  : T

type UppercaseChar<Char extends string> = Char extends 'a'
  ? 'A'
  : Char extends 'b'
  ? 'B'
  : Char extends 'c'
  ? 'C'
  : Char extends 'd'
  ? 'D'
  : Char extends 'e'
  ? 'E'
  : Char extends 'f'
  ? 'F'
  : Char extends 'g'
  ? 'G'
  : Char extends 'h'
  ? 'H'
  : Char extends 'i'
  ? 'I'
  : Char extends 'j'
  ? 'J'
  : Char extends 'k'
  ? 'K'
  : Char extends 'l'
  ? 'L'
  : Char extends 'm'
  ? 'M'
  : Char extends 'n'
  ? 'N'
  : Char extends 'o'
  ? 'O'
  : Char extends 'p'
  ? 'P'
  : Char extends 'q'
  ? 'Q'
  : Char extends 'r'
  ? 'R'
  : Char extends 's'
  ? 'S'
  : Char extends 't'
  ? 'T'
  : Char extends 'u'
  ? 'U'
  : Char extends 'v'
  ? 'V'
  : Char extends 'w'
  ? 'W'
  : Char extends 'x'
  ? 'X'
  : Char extends 'y'
  ? 'Y'
  : Char extends 'z'
  ? 'Z'
  : Char

余談 2
Uppercase は ts では以下のように実装されている

type Uppercase<S extends string> = intrinsic

intrinsic はコンパイラの内部実装として隠蔽されていることを示す

詳細: https://zenn.dev/uhyo/articles/typescript-intrinsic

ユースケース例

  1. 特定のファイル形式のみ返す関数を作る
type FileExtension = 'jpg' | 'jpeg' | 'png' | 'gif'
type getFile = () => `${string}.${FileExtension}`
  1. hex のカラーコードのみ受け取る関数
type Darken = (hex: `#${string}`) => `#${string}`

型での表現力が増して良いですね

まとめ

流石の表現力でした。Template Literal Types を使うことでより堅牢な設計ができるようになりそうです。

0
0
0

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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?