• トップ
  • ブログ一覧
  • TypeScript4.1(beta)で追加された新機能とは?
  • TypeScript4.1(beta)で追加された新機能とは?

    広告メディア事業部広告メディア事業部
    2020.11.21

    エンジニアになろう!

    TypeScript4.1(beta)の新機能とは?

    2020年11月に、最新バージョン「4.1」の beta 版がリリースされた TypeScript。

    最新版には、以下の7機能が追加されました。

    1. Template string types
    2. Key Remapping in Mapped Types
    3. Recursive Conditional Types
    4. --noUncheckedIndexedAccess
    5. paths without baseUrl
    6. checkJs Implies allowJs
    7. React 17 JSX Factories

    今回は、上記のうち特に注目されている3つの機能をメインに、「TypeScript 4.1」の新機能を解説していきます!

    新機能1:Template string types

    「Template string types」は、テンプレートリテラルを使って型を定義できる機能です。

    具体的には、以下のように、変数展開をする感じです。

    1type TypeScript = 'TypeScript'
    2
    3type HelloTs = `Hello, ${TypeScript}`
    4// => type HelloTs = 'Hello, TypeScript'
    5
    6type Join<T extends string, S extends string> = `${T}, ${S}`
    7type HelloTs2 = Join<'Hello', 'TypeScript'> 
    8// => type HelloTs2 = 'Hello, TypeScript'

    全ての組み合わせの型が作成可能

    さらに、Union Type を引数にすると、こんなこともできちゃいます!

    1type Hello<T extends string> = `Hello, ${T}`;
    2type T1 = Hello<'foo' | 'bar' | 'baz'>;
    3// => type T1 = 'Hello, foo' | 'Hello, bar' | 'Hello baz';

    では、Union Type を2つ定義した場合はどうなるのでしょうか?

    1type T3 = `${'top' | 'bottom'}-${'left' | 'right'}`;

    結果を見てみましょう!

    1type T3 = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'

    ご覧のように、「Template string types」を使えば、全ての組み合わせの型を作ることができるのです。

    数学の分配法則とよく似ていますね。

    新機能2:Key Remapping in Mapped Types

    「Key Remapping in Mapped Types」は、asキーワードに Mapped Types を使い、プロパティ名を再定義できる機能です。

    4.0 以前の記述

    例として、Partial の型定義をしてみましょう。

    TypeScript 4.0以前の Mapped Types は、以下のように記述していました。

    1type Partial<T> = {
    2    [K in keyof T]?: T[K]
    3};

    これでは、プロパティ名は T 型のキー名でしか生成することができません。

    4.1の記述

    TypeScript 4.1からは「Key Remapping in Mapped Types」を使えば、自由にプロパティ名を設定することができます。

    プロパティ名に任意の名前をつける書き方は、以下のように記述してください。

    1type MappedTypeNewKeys<T> = {
    2    [K in keyof T as NewKeyType]: T[K]
    3}

    NewKeyType の部分に、自分の指定したいプロパティ名を設定できます。

    具体的には、以下のような使い方をします。

    1type Getters<T> = {
    2    [K in keyof T as `get${capitalize K}`]: () => T[K]
    3};
    4
    5interface Person {
    6    name: string;
    7    age: number;
    8    location: string;
    9}
    10
    11type LazyPerson = Getters<Person>;

    こちらの例では、Person 型のプロパティを全て Getter にした例です。

    as を使って、Person 型プロパティ名の先頭を capitalize で大文字にしてから、そのプロパティ名の先頭に get を追加しています。

    また、capitalize の他にも、以下のようなものが使用可能なので、覚えておくと良いでしょう。

    1. uppercase:全て大文字にする
    2. lowercase:全て小文字にする
    3. uncapitalize:先頭を小文字にする

    新機能3:Recursive Conditional Types

    「Recursive Conditional Types」は、再帰的に Conditional Type を宣言できるようにした機能です。

    TypeScript 4.0以前では、出来ないわけではなかったものの、記述がかなり複雑になってしまうため、自然な記述ができるようになったのはありがたいですね。

    1type ElementType<T> = T extends ReadonlyArray<infer U> ? ElementType<U> : T;

    この例では、T が読み込み専用の配列なら、その中身の型を取り出すようになっています。

    ネストが深い配列も型安全に処理できる

    入れ子構造になっている配列を、フラットにするような関数の型も表現できます。

    1function deepFlatten<T extends readonly unknown[]>(x: T): ElementType<T>[] {
    2    throw "ここに実装を書きます";
    3}
    4
    5deepFlatten([1, 2, 3]);
    6deepFlatten([[1], [2, 3]]);
    7deepFlatten([[1], [[2]], [[[3]]]]);

    この deepFlatten 関数は、引数[1, 2, 3][[1], [2, 3]][1], [[2]], [[[3]]]] のどれをとっても、返り値は number 型になります。

    では、deepFlatten 関数の型は一体どうなっているのでしょうか?

    それぞれの引数で、具体的に関数の型がどうなっているのかを見てみましょう。

    1function deepFlatten<number[]>(x: number[]): number[] {
    2    throw "ここに実装を書きます";
    3}
    4deepFlatten([1, 2, 3]);
    1function deepFlatten<number[][]>(x: number[][]): number[] {
    2    throw "ここに実装を書きます";
    3}
    4deepFlatten([[1], [2, 3]]);
    1function deepFlatten<number[] | number[][] | number[][][][]>(x: number[] | number[][] | number[][][][]): number[] {
    2    throw "ここに実装を書きます";
    3}
    4deepFlatten([[1], [[2]], [[[3]]]]);

    このように、どれだけネストの深い配列がきたとしても、再帰的な型定義をすれば、型安全に処理することができるのです。

    まだまだある、その他の新機能

    以上、特に注目の高い新機能を見てきましたが、TypeScript 4.1にはまだまだ便利な新機能が追加されています。

    --noUncheckedIndexedAccess

    「--noUncheckedIndexedAccess」は、TypeScript 4.1のコンパイラオプションです。

    undefined になりうる全てのインデックスアクセスに対して、エラーを発生させます。

    例えば、以下のコードでもエラーが発生します。

    1function showLines(strs: string[]) {
    2    for (let i = 0; i < strs.length; i++) {
    3        console.log(strs[i].toLowerCase());
    4        //          ~~~~~~~
    5        // strs[i]はundefinedになりうるので、エラー
    6    }
    7}

    この場合、for 文ではなく、forEach や for...of 構文を使えば、エラーを回避できます。

    場合によってはオフにしておいた方がいいオプションなので、必要な時にだけオンにするといいでしょう(ちなみに、デフォルトではオフになっています)。

    paths without baseUrl

    tsconfig.json のプロパティにある「paths」に関する機能です。

    paths は、モジュールなどを import する際、パスを簡略化したい時などに使われます。

    この新機能では、その paths に baseUrl を指定する必要がなくなりました。

    checkJs Implies allowJs

    4.0以前は、JavaScript に型を導入したい時、allowJs と checkJs の2種類のオプションがありました。

    TypeScript 4.1 からは、デフォルトで checkJs は allowJs を指すようになります。

    React 17 JSX Factories

    React 17における jsxs・jsx のファクトリ関数をサポートします。

    tsconfig.json の「compilerOptions」に、「react-jsx」のように指定すれば使えるようになります。

    1{
    2    "compilerOptions": {
    3        "module": "esnext",
    4        "target": "es2015",
    5        "jsx": "react-jsx",
    6        "strict": true
    7    }
    8}

    さいごに

    この記事では、TypeScript 4.1の新機能をご紹介しました。

    「Template string types」のような手軽に使えて柔軟性が高いものから、「Recursive Conditional Types」のような再帰的に型を定義できるものまで、知ってみると面白いものばかりです。

    また、新機能を使ったこんな興味深いサイトもあるので、ぜひ参考にしてみてくださいね。

    【TypeScript 型パズルで作るmini interpreter】
    https://medium.com/@Quramy/ネタ-typescript-型パズルで作るmini-interpreter-f21854cf3189

    【TS 4.1 の機能で雑に型の足し算・引き算を作る】
    https://www.pg-fl.jp/program/tips/ts_typecalc.htm

    TypeScript 4.1の新機能を知って、より深い型の世界に浸っていきましょう!

    こちらの記事もオススメ!

    featureImg2020.07.17ライトコード的「やってみた!」シリーズ「やってみた!」を集めました!(株)ライトコードが今まで作ってきた「やってみた!」記事を集めてみました!※作成日が新し...
    featureImg2020.08.07JavaScript 特集知識編JavaScriptを使ってできることをわかりやすく解説!JavaScriptの歴史【紆余曲折を経たプログラミン...

    広告メディア事業部

    広告メディア事業部

    おすすめ記事