TypeScript5.0がリリースされたのでリリースノートを読んでみる

2023年3月16日、TypeScriptのバージョン5.0のリリースがアナウンスされました！
[Announcing TypeScript 5.0](https://devblogs.microsoft.com/typescript/announcing-typescript-5-0/)
この記事では、リリースノートを読んで気になった項目についていくつか取り上げてみたいと思います。
内容についてはあくまで個人の感想ですので、正式な情報はリリースノートやソースコードを参照してください。

# デコレータ

今回のリリースの目玉機能の一つがデコレータです。
デコレータは`@xxxx`のようなアノテーションをつけることで既存のクラスやメソッドを拡張することができる機能です。
JavaやPythonなど他の言語ではお馴染みの機能かもしれません。

リリースノートでは例としてメソッドにデバッグログを付与するデコレータが紹介されています。
以下のようにデコレータのメソッドを定義して
```typescript
function loggedMethod(originalMethod: any, _context: any) {

    function replacementMethod(this: any, ...args: any[]) {
        console.log("LOG: Entering method.")
        const result = originalMethod.call(this, ...args);
        console.log("LOG: Exiting method.")
        return result;
    }

    return replacementMethod;
}
```

メソッドに`@loggedMethod`とデコレータを付与すると、デバッグログを出力するように機能拡張されます。

```typescript
class Person {
    name: string;
    constructor(name: string) {
        this.name = name;
    }

    @loggedMethod
    greet() {
        console.log(`Hello, my name is ${this.name}.`);
    }
}

const p = new Person("Ron");
p.greet();

// Output:
//
//   LOG: Entering method.
//   Hello, my name is Ron.
//   LOG: Exiting method.
```

他にも引数を取ったり、複数のデコレータを同時に付与することも可能です。

```typescript
// デコレータに引数を渡すことができる
@loggedMethod("xxx")
greet() {
    console.log(`Hello, my name is ${this.name}.`);
}
----------------------------------------------------------------
// 複数のデコレータを付与できる
@bound
@loggedMethod
greet() {
    console.log(`Hello, my name is ${this.name}.`);
}
```

なお、これまでにもデコレータは利用可能でした。
これはJavaScriptの標準化を進めているTC39のStage2(Draft)の仕様に沿った実験的な機能でした。
この仕様が昨年Stage3(Candidate)に上がったことを受け、TypeScript5.0でStage3に沿った実装が行われたという流れです。
Stage2とStage3ではデコレータの実装方法や付与できる対象に違いがあるため、今後はこれらの違いを意識して使う必要がありそうです。
特にメソッドの引数に対するデコレータはまだ対応されておらず、これを使いたい場合はStage2にとどまる必要があります。
これらの使い分けはコンパイラの`--experimentalDecorators`のフラグで制御でき、デフォルトでStage3,フラグをtrueにすると従来通りStage2のデコレータを使うことができます。
TypeScript5.0以前からデコレータを使う場合`--experimentalDecorators`をtrueにする必要があったため、この部分は特に変更なくアップデートできるようになっています。


# const Type Parameters

5.0から型変数に`const`が使えるようになりました。
これを用いると推論がより具体的な型になります。

```typescript
// constなし
function unuseConst<T extends readonly string[]>(arg: T): T {
    return arg;
}
// constあり
function useConst<const T extends readonly string[]>(arg: T): T {
    return arg;
}

// Inferred type: string[]
const unused = unuseConst(["a", "b", "c"]);
// Inferred type: ["a", "b", "c"]
const used = useConst(["a", "b", "c"])
```

これまでにも`const assertion`を用いて同様のことはできましたが、const Type Parametersを使うことで毎回`as const`と書く必要がなくなります。書き忘れも防止できますし、なによりシンプルです。

```typescript
// constなし
function unuseConst<T extends readonly string[]>(arg: T): T {
    return arg;
}

// これでも ["a", "b", "c"]型に推論はされるが、毎回 as constと書く必要がある
const unused = unuseConst(["a", "b", "c"] as const)
```

と、ここまで書きましたが正直この機能を活かせるところがあまり思いつきませんでした、、、。パッケージの開発などで便利なのかもしれません。

# tsconfig.jsonでの複数のextendsのサポート

TypeScriptでは、コンパイラが使用する設定ファイルとして`tsconfig.json`を使用します。
この中でも`extends`フィールドを使用することで、複数のプロジェクトで共通の設定を再利用できます。

例えば以下のように書くと、`tsconfig.base.json`のような複数のプロジェクト共通の設定を基礎としながら、プロジェクトごとに個別で設定したい項目だけ変更できます。

```json
{
  "extends": "./tsconfig.base.json",
  "compilerOptions": {
    "target": "es6",
    "module": "commonjs",
    "outDir": "./dist"
  }
}
```

これまでは`extends`には一つのファイルしか書けませんでしたが、5.0からは複数のファイルを配列で設定できるようになりました。

```json
{
    "extends": ["a", "b", "c"],
    "compilerOptions": {
        // ...
    }
}
```

これを使うことで、フレームワークやNodeのバージョンごとに用意されている[プリセット](https://github.com/tsconfig/bases)を複数組み合わせて良いとこどりができそうです。

なお、config内の同じ項目については、extendsの配列の中で後勝ちになります。
そのため以下のように書くと`strictNullChecks`はfalseになるはずです。

```json
// tsconfig.a.json
{
    "compilerOptions": {
        "strictNullChecks": true
    }
}

// tsconfig.b.json
{
    "compilerOptions": {
        "strictNullChecks": false
    }
}

// tsconfig.json
{
    "extends": ["./tsconfig.a.json", "./tsconfig.b.json"],
    "files": ["./index.ts"]
}
```

# おわりに

気になった変更を3つ紹介させていただきました！
この他にも様々なアップデートが入っていますのでぜひリリースノートを読み、バージョンアップしてみてください！