thumbnail

TypeScript4.5~4.0の新機能まとめ

最新のTypeScript4.5から、TypeScript4.0に遡って新機能をまとめています。まとめてある内容は文法レベルの新機能で、細かい仕様変更についてはまとめていません。ざっと新機能だけを確認するのが目的です。記事の後ろに行けば行くほど古いバージョンについて書いているため、知っているバージョンまでスクロールする必要はありません。各見出しの後には、公式ドキュメント(英語)に対応する項目を記載しております。

TypeScript 4.6以降について

長くなったので別記事にまとめてあります。

TypeScript 4.5

Awaited型とPromiseの改良

項目名:「The Awaited Type and Promise Improvements」

Awaited型が導入されました。Promiseを再帰的にアンラップできます。

type A = Awaited<Promise<number>> // A = number
type B = Awaited<Promise<Promise<string>>> // B = string 
type C = Awaited<string | Promise<string> | PromiseLike<string>> // C = string

これによってPromise.allをうまく推論してくれます。

declare function MaybePromise<T>(value: T): T | Promise<T> | PromiseLike<T>
async function doSomething(): Promise<[number, number]> {
  const result = await Promise.all([MaybePromise(100), MaybePromise(200)])
  // resultの型は4.4以前では
  // [number | Promise<100>, number | Promise<200>]
  // と推論されてエラーが出る
  // 4.5ではAwaited<MaybePromise<T>(value: T): T | Promise<T> | PromiseLike<T>>
  // = T となりうまく推論される
  return result
}

TypeScript 4.4

Symbol型とテンプレートリテラル型をキーとするインデックスシグネチャ

項目名:「Symbol and Template String Pattern Index Signatures」

インデックスシグネチャのキーに、Symbol型とテンプレートリテラル型を利用できるようになりました。従来はnumber型とstring型のみでした。

interface Options {
  //キーが""data-"から始まるプロパティのみ受け付ける
  [key: `data-${string}`]: number,
  //値がSymbol型のキーも受け付ける
  [key: symbol]: string
}
let a: Options = {
  'data-hoge': 0,
  'data-fuga': 2,
  // 'piyo': 3 //エラー,
}
//キーとして利用
const mySymbol = Symbol()
a[mySymbol] = "symbol"

クラスにstaticブロックの追加

項目名:「Static Blocks in Classes」

クラスにstaticブロックを作成できるようになりました。このブロックはクラス定義をするだけで呼び出されます。staticメンバの初期化等に使えます。

class MyClass {
  static {
    console.log("hoge") //インスタンスを作成しないでも出力される
  }
}

TypeScript 4.3

ゲッタとセッタの型を別々に指定できる

項目名:「Separate Write Types on Properties」

ゲッタとセッタの型に別の型を定義することが可能となります。
従来は、「セッタの引数の型」と「ゲッタの戻り値の型」は統一する必要があり、型を明示していない場合にも自動的に統一されていました。

4.3からは「ゲッタの戻り値の型」と「セッタの引数の型」を別の型にすることが出来ます。ただし、「ゲッタの戻り値の型」は「セッタの引き数の型」に割り当て可能でなければなりません。

class MyClass {
    #size: number = 0
    get size(): number { //従来は number | stringしか指定できなかった
        return this.#size
    }
    set size(n: number | string) {
        if (typeof n === "string") {
            this.#size = Number(n)
        } else if (typeof n === "number")
            this.#size = n
    }
}

override修飾子

項目名:「override and the –noImplicitOverride Flag」

オーバーライドするメソッドにoverrideキーワードをつけると、オーバーライドであることを明示的に宣言できるようになりました。overrideを付けたメソッドが先祖で定義されていなければエラーを出してくれます。

class MyClass {
  protected hoge() {}
  public fuga() {}
}
class MyChild extends MyClass{
  override hoge() {}
//    override piyo() {} //親クラスで定義されていないのでエラー
}

また、–noImplicitOverride Flagをつけるとオーバーライドするメソッドにoverrideキーワードの付与を強制できます。

公式ページでoverrideの説明と一緒にフラグの紹介がされていたため、まとめて紹介しました。

テンプレートリテラル型の改良

項目名:「Template String Type Improvements」

テンプレートリテラル型の改善です。
次のような代入が可能となります。

declare let s1: `${number}-${number}-${number}`;
declare let s2: `${number}-2-3`;
s1 = s2;

TypeScript 4.2

タプルのレストパラメータを好きな位置に記述できる

項目名:「Leading/Middle Rest Elements in Tuple Types」

タプルのレストパラメータを、要素の好きな位置に置けるようになりました。
4.1以前はタプルの最後にしか置けませんでした。

//最初に置く
let ary: [...string[], number]
ary = [2]
ary = ["hoge", 4]
ary = ["hoge", "fuga", 10]
//間に置く
let ary2: [string, ...number[], boolean]
ary2 = ["hoge", true]
ary2 = ["hoge", 2, false]
ary2 = ["hoge", 10, 23, 2, false]

この機能を利用すれば、レストパラメータを最初に受け取る関数を定義することが出来ます。

function hoge(...args: [...nums: number[], flag: boolean]) {
    let nums = args.slice(0, args.length-1)//先行レストパラメータ
    let flag = args[args.length]
    //
}

ただし、レストパラメータは1つしか使用できません。
また、オプショナルパラメータを使う場合はレストパラメータを最後以外には置けません。

// let ary3: [string, ...number[], ...string[]] //エラー:レストパラメータが2つある
// let ary4: [boolean, ...number[], string?] //エラー:オプショナルパラメータがあるのにレストパラメータが最後にない
let ary5: [boolean?, ...number[]] // OK

abstractコンストラクタシグネチャ

項目名:「abstract Construct Signatures」

コンストラクタシグネチャにabstact修飾子が使えるようになりました。
従来は以下のようなコンストラクターシグネチャに、abstract修飾子のついた抽象クラスを代入することが出来ませんでした。

abstract class Person {
    abstract hoge(): string
}
class MyPerson extends Person {
    public hoge() {
        return "hoge"
    }
}
//コンストラクターシグネチャを引数に取る関数
function func(Cls: new () => Person) {
    return class extends Cls {
        hoge() {
            return "func"
        }
    } 
}
// func(Person) //エラー:抽象クラスは渡せない
func(MyPerson) //具象クラスはOK

newの前にabstract修飾子をつけることで、抽象クラスも渡せるようになりました。

abstract class Person {
    abstract hoge(): string
}
class MyPerson extends Person {
    public hoge() {
        return "hoge"
    }
}
//コンストラクターシグネチャを引数に取る関数
function func(Cls: abstract new () => Person) {
    return class extends Cls {
        hoge() {
            return "func"
        }
    } 
}
func(Person) // 抽象クラスも渡せる
func(MyPerson) //具象クラスもOK

TypeScript 4.1

テンプレートリテラル型の追加

項目名:「Template Literal Types」

型にテンプレートリテラル(`${hoge}`)が使用できるようになりました。

type Name = "Taro"
type Age = 10
type Person = `${Name} ${Age}` //"Taro 10"型

Union型と使うと、全ての組み合わせのUnion型になります。

type Name = "Taro" | "Jiro" | "Hanako"
type Age = 10 | 20 
type Person = `${Name} ${Age}` 
//"Taro 10" | "Taro 20" | "Jiro" 10 | "Jiro" 20 | "Hanako" 10 | "Hanako 20"型 

次のようにフォーマッタとしても使えます。

type CurrentTime = `${string}:${string}:${string}`
const now: CurrentTime = '10:25:23'
// const now: CurrentTime = '10,25,23' エラー

inferと使えば、型レベルで部分文字列を取得するようなコードも書けます。

type Person<T extends string> = T extends `${infer Name} is ${infer Age} years old.` ? {name: Name, age: Age} : never
type Taro = Person<"Taro is 16 years old."> //{name: "Taro", age: "16"}型
type Hanako = Person<"Hanako is 20 years old.">  //{naem: "Hanako", age: "20"}型

Map型のキー名を変更できる

項目名:「Key Remapping in Mapped Types」

Map型でas句を使用することによって、キーの名前を変更することが出来ます。

type Getters<T> = {
    [K in keyof T as 新しいキー名]: T[K]
}

次の例は、既存のインターフェースのプロパティ名を使って新しいプロパティで新しいキー名を作成しています。

type Getters<T> = {
    [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]
}
interface Person {
    name: string
    age: number
    location: string
}
type NewPerson = Getters<Person>
/**
 * 以下と同じ
 * type NewPerson = {
 *  getName: () => string
 *  getAge: () => number
 *  getLocation: () => string
 * }
 * 
 */

Excludeを使ってプロパティのフィルタリングも出来ます。

type Getters<T> = {
    [K in keyof T as Exclude<K, "age">]: T[K]
}
interface Person {
    name: string
    age: number
    location: string
}
type NewPerson = Getters<Person>
/**
 * type NewPerson = {
 *  name: string
 *  location: string
 * }
 * 
 */

再帰的なConditional 型

項目名:「Recursive Conditional Types」

条件型を再帰的に呼び出せるようになりました。

これによって、深いネストの配列も安全にフラットにすることが可能となります。

//ElementTypeを右辺でも再帰的に呼び出している
type ElementType<T> = T extends ReadonlyArray<infer U> ? ElementType<U> : T
function deepFlatten<T extends readonly unknown[]>(x: T): ElementType<T>[] {
  throw "not implemented";
}
//全てnumber[]型になる
deepFlatten([1, 2, 3]);
deepFlatten([[1], [2, 3]]);
deepFlatten([[1], [[2]], [[[3]]]]);

TypeScript 4.0

可変タプル型

項目名:「Variadic Tupple Types」

タプルに関する型レベルの機能の追加です。

TypeScript3.9までの型レベルのタプルとスプレッド演算子の仕様には次のような制約がありました。

  1. ①スプレッド演算子が使えるのはタプルの最後の要素のみ
  2. ②必ず...T[]のようにブラケット[]を付ける必要がある

よって使える場面は限られており、次のように可変長タプルとしてしか利用できませんでした。

type hoge = [number, string, ...number[]]

4.0ではこれらの制約は無くなり、以下のような使い方ができます。

①タプルの中で...Tのようにスプレッド演算子が使える

次のような記述が可能になりました。

type A = [number, string]
type B = boolean[]
type C = [...A, ...B, number] // [number, string, ...bollean[], number]型

②ジェネリックに...Tのようなスプレッド演算子が使える

これによって、より汎用的な型の定義が可能となります。

まずは簡単な使用例を載せます。

type Hoge<T extends readonly any[]> = [number, ...T]
type Fuga<T extends readonly any[]> = [string, ...T, number]
type Repeat<T extends readonly any[]> = [...T, ...T]
type repeat1 = Repeat<[number, string]> //[number, string, number, string]型

ジェネリックを使った関数の仮引数で、[...T]と指定することによってタプル型を推論できるようにもなります。

function func<T extends readonly any[]>(arr: [...T]): [...T, ...T] {
    return [...arr, ...arr]
}

条件型において...infer Uのような記述も可能となります。

type Tail<T extends readonly [any, ...any[]]> = T extends [any, ...infer U] ? U : never 

この新機能は非常に奥が深く、さまざまな表現が可能となります。
次のQiitaの記事がとても参考になります。

ラベル付きタプル

項目名:「Labeled Tuple Element」

タプルの要素に名前がつけられるようになりました。

type tup = [first: number, second: string]

オプション、可変長にも対応しています。

type tup2 = [first: number, second?: string, ...last: boolean[]]

Labeled Tupleを使うことで可読性が向上します。

//それぞれのstringが意味するところが分からない
function hoge(...name: [string, string]) {}
//それぞれのstringの意味が分かる
function fuga(...name: [firstName: string, lastName: string]) {}

ただし、あくまで”ラベル”なので名前を使ったタプルの参照はできません。
使い方は従来のタプルと変わりません。

function fuga(...name: [firstName: string, lastName: string]) {
  // const first = firstName エラー
  const first = name[0] 
}

コンストラクタからプロパティの型を推論

項目名:「Class Property Inference from Constructors」

コンストラクタで定義されているプロパティの型が自動的に推論されるようになりました。
以前はany型となっていました。

class Person {
    name;//string型になる!!!!
    constructor(name: string){
        this.name = name
    }
}

短絡演算、Null合体演算の複合演算子

項目名:「Short-Circuiting Assignment Operators」

次の3つの複合演算子が追加されました。

a ||= b // a || a = b  :aがfalsyなら、aにbが代入される
a &&= b  //  a && a = b :aがtruthyなら、aにbが代入される
a ??= b  // a ?? a = b  :aがnullなら、aにbが代入される

catchするエラーの型をunknown型にできる

項目名:「Unknown on catch Clause Buildings」

3.9まではcatch句で補足するエラーがany型で、安全ではありませんでした。

//3.9まで
//実行するまでエラーが出ない
try {
    //
} catch (e) {
    e++
    e.toUpperCase()
    e.hoge.hoge.fuga()
}

4.0からcatch句で補足するエラーにunknown型を指定できるようになり、型チェックしてからでないと演算ができなくなりました。

try {
  //
} catch (e: unknown) {
  if (typeof e === "number") {//型チェック
    e++
  } else if (typeof e === "string") {//型チェック
    e.toUpperCase()
  }
}

参考

公式ドキュメント

https://www.typescriptlang.org/docs/handbook/release-notes/overview.html