【TypeScript 4.3 Beta】Template Literal Types
公式ブログで TypeScript 4.3 Beta が発表されました。
この記事では、この内の Template Literal Types だけ取り上げて、どのような変更があったのかを書いていきたいと思います。
その他の変更点については、こちらの記事で取り上げています。
Template String Type Improvements
Template Literal Types が具体的に以下の2点で改善されました。
- 文脈的に Template Literal Types かどうか判断されるようになった
- 異なる Template Literal Types 間の関係と推論が改善された
そもそも Template Literal Types ってなんだっけ?
JavaScript では、テンプレートリテラルを使って、以下のように変数などの式を文字列内に埋め込むことができます。
const name = 'aki';
console.log(`Hello, ${name}!`) // Hello, aki!
v4.1 に新しく導入された Template Literal Types は、JavaScript のテンプレートリテラルと同じ構文で、型を生成することができます。
type Animal = "shark" | "giraffe" | "platypus";
type BabyAnimal = `baby-${Animal}`; // "baby-shark" | "baby-giraffe" | "baby-platypus"
上記のように、リテラル型と合わせて使うことで、内容を結合して新しい文字列リテラル型を生成することができます。
TypeScript 4.2 Beta では
v4.2 Beta でテンプレートリテラルを書いたとき、その型は Template Literal Types という扱いになっていました。
const n: number = 123;
// value1 は `${number}px` 型
const value1 = `${n}px`;
// value2 は string 型
let value2 = `${n}px`;
ただし、上記の value2
のように、let
に代入する場合は Template Literal Types ではなく string
として扱われます。let
は再代入可能なので、型を広げる ( 公式ブログでは widening という表現を使っていた ) ためだと思われます。
TypeScript 4.2 では
しかし、v4.2 で revert されることになりました。
テンプレートリテラルを const
で定義した場合、常に Template Literal Types になるというのは望ましくないケースもあるということで、デフォルトでは string
として扱われることになりました。
const n: number = 123;
// value1 は string 型
const value1 = `${n}px`;
// value2 は string 型
let value2 = `${n}px`;
// value3 は `${number}px` 型
const value3 = `${n}px` as const;
その代わり value3
のように、末尾に as const
をつけた場合は Template Literal Types になるように変更されました。
1. 文脈的にTemplate Literal Typesかどうか判断されるようになった
以上を踏まえて、v4.3 Beta では Template Literal Types がどのように変更されたか見ていきたいと思います。
v4.3 Beta 以前
v4.2 では as const
をつけない限り Template Literal Types にはならないので、以下のコードはエラーになっていました。
function bar(s: string): `hello ${string}` {
// Error! Type 'string' is not assignable to type '`hello ${string}`'.
return `hello ${s}`;
}
v4.3 Beta 以降
v4.3 Beta では、「文脈的に Template Literal Types しかありえないよね」という場合 ( 公式ブログでは contextually typed という表現 ) には、as const
が無くても Template Literal Types が適応されるようになりました。
なので先ほどはエラーだったこちらのコードも、v4.3 Beta では問題なく通るようになります。
function bar(s: string): `hello ${string}` {
// OK! \(^o^)/
return `hello ${s}`;
}
【 Playgroundで確認する 】 contextuallyすごい 👏
2. 異なる Template Literal Types 間の関係と推論が改善された
v4.3 Beta 以前
今までは、target 側 が Template Literal Types であるとき、source 側も Template Literal Types にすることはできませんでした。
以下の例では、s1
が target 側、s2
が source 側になります。
declare let s1: `${number}-${number}-${number}`;
declare let s2: `1-2-3`;
s1 = s2;
s2
は バッククォートで囲まれているので、一見 Template Literal Types に見えますが、マウスカーソルを合わせると、実際には文字列リテラル型であると分かります。
文字列リテラル型の値を Template Literal Types の変数に代入することは可能なので、上記のコードは問題なく通ります。
しかし、target 側 (s1
) も source 側 (s3
) も両方 Template Literal Types である場合は、以下のようにエラーになります。
declare let s1: `${number}-${number}-${number}`;
declare let s3: `${number}-2-3`;
s1 = s3; // Error! Type '`${number}-2-3`' is not assignable to type '`${number}-${number}-${number}`'.
v4.3 Beta 以降
v4.3 Beta では、target 側と source 側で両方とも Template Literal Types だったとしても、怒られなくなりました。
TypeScript が互換性があるかどうか判断できるようになったため、以下のように ${...}
を自由に組み合わせることもできます。
declare let s1: `${number}-${number}-${number}`;
declare let s2: `1-2-3`;
declare let s3: `${number}-2-3`;
declare let s4: `1-${number}-3`;
declare let s5: `1-2-${number}`;
declare let s6: `${number}-2-${number}`;
// すべてOK! \(^o^)/
s1 = s2;
s1 = s3;
s1 = s4;
s1 = s5;
s1 = s6;
Discussion