「Announcing TypeScript 4.8」を読む

# 「Announcing TypeScript 4.8」を読む

[この記事](https://devblogs.microsoft.com/typescript/)を知った翌日に4.8がリリースされていたので、勉強を兼ねて自分なりに読んでまとめてみようと思います。

本記事は、次のページの内容を自分なりにまとめたものです。
<https://devblogs.microsoft.com/typescript/announcing-typescript-4-8/>

## [Improved Intersection Reduction, Union Compatibility, and Narrowing](https://devblogs.microsoft.com/typescript/announcing-typescript-4-8/#smarter-type-narrowing-and-simplifications)

`{}`型関係の挙動が改善されました。

- `{} | null | undefined`型の変数に`unknown`型の変数が代入できるようになりました。

```typescript
function test(a: unknown, b: {} | null | undefined){
  a = b; // 以前からOK。unknownはanyと同じでなんでも代入できる。
  b = a; // 以前はNG、4.8からOK
}
```

- `{}`型とobject typeのintersection(交差型)が、単に相手のobject typeに推論されるようになりました。(*1)
  これにより、`NonNullable<T>`型の実装が変わっています。

```typescript
type NonNullable<T> = T extends null | undefined ? never : T; // ～4.7
type NonNullable<T> = T & {}; // 4.8～
```

- `unknown`型変数をif文でチェックした場合、true branchのほうは`{}`型にnarrowingされるようになりました。

```typescript
function test(a: unknown){
  if (a) {
    a; // 前はunknown型、4.8は{}型
  } else {
    a; // unknown型。
  }
}
```

- Generic型についても同様に、`null`と`undefined`のいずれでもないと推定される変数は、`{}`との交差型と推定されます。

```typescript
function test<T>(a: T): NonNullable<T> {
  if (a === null || a === undefined) {
    throw Error();
  }
  // 以前はT型と推論されるため、戻り値 NonNullable<T> 型と合わず型エラーとなる。
  // 4.8以降はT & {}型に推論されるため、型エラーにならない。
  return a;
}
```

(*1: 書き方を見るに、null,undefined以外の型をobject typeというらしいです。)

## [Improved Inference for infer Types in Template String Types](https://devblogs.microsoft.com/typescript/announcing-typescript-4-8/#infer-types-template-strings)

Template文字列型の中でinfer～extendsを使った時、可能であれば具体的なリテラル型に変換されるようになりました。

ただし、`'1.0'`というリテラル型にはうまくはまらないそうです。元の文字列をプリミティブ値に変換して、もう一度文字列に戻した時に、元の文字列と等しくないと、リテラル型にならないそうです。`'1.0'`型は、数値型にすると`1`型になるので、それを文字列化しても元と一致しないということですね。

いくつかテストしてみました。動作は以下のようです。

```typescript
type Test<T> = T extends `${infer U extends number}` ? U : never;

type T1 = Test<'0'>; // 0
type T2 = Test<'0' | '4'>; // 0 | 4
type T3 = Test<'-4'>; // -4
type T4 = Test<'0' | '4' | 'number'>; // 0 | 4
type T5 = Test<'1.0'>; // number
type T6 = Test<'NaN'> // never
type T7 = Test<'Infinity'> // never
```

## [Errors When Comparing Object and Array Literals](https://devblogs.microsoft.com/typescript/announcing-typescript-4-8/#object-array-comparison-errors)

`==`と`===`の比較でオブジェクトや配列のリテラルを使うと、エラーとなります。
JavaScriptはオブジェクト・配列の比較が参照比較なので、リテラルで今作ったオブジェクトと変数の参照が一致することはなく、必ずfalseになるif文が書けなくなります。これはいいですね！

```typescript
if (someArray === []) foo();
 // ~~~~~~~~~~~~~~~~~ エラーになる。
```

## [Improved Inference from Binding Patterns](https://devblogs.microsoft.com/typescript/announcing-typescript-4-8/#inference-binding-patterns)

パッと読んで良く分からなかったので、テストコードを書いてみました。

```typescript
const sample = <T>(a: T, b: T): T => a;
const [a, b, c] = sample([0, 'a', true], [1, 'b', false]); // 4.8以降もOK

const sample2 = <T>(t?: T): T => {
  if (!t) throw new Error();
  return t;
}
const [d, e, f] = sample2(); // 4.7はOK、4.8ではNG。
               // ~~~~~~~ ←ここの型推論が変わる。
```

それぞれの型推論を見てみると、`[d, e, f] = sample2();`としているところのsample2の型が、4.7では`<[any, any, any]>(t?: [any, any, any] | undefined) => [any, any, any]`型、4.8では`<unknown>(t?: unknown) => unknown`型と推論されます。

今までは、`[d,e,f]`に分割代入しているということが優先度の低い型推論として型パラメータ`T`を`[any, any, any]`と推論していましたが、それがなくなると書いてあります。こんなケースに遭遇するかは分かりませんが、微妙な場合はちゃんと型変数を明示したほうがよさそうですね。

## [Exclude Specific Files from Auto-Imports](https://devblogs.microsoft.com/typescript/announcing-typescript-4-8/#exclude-globs-auto-import)

エディターに設定を追加すると、特定のファイルの型を除外できるようになりました。コンパイルには必要だけど、importはしないファイルとかを設定しておくらしいです。設定方法はリンク先にあるので、本記事では割愛します。

## [lib.d.ts Updates](https://devblogs.microsoft.com/typescript/announcing-typescript-4-8/#lib-d-ts-updates)

いくつか変更があったそうですが、ノートには`Error.cause`の型が`Error`から`unknown`に変更されたことが一つの目玉として紹介されています。

## [Unconstrained Generics No Longer Assignable to {}](https://devblogs.microsoft.com/typescript/announcing-typescript-4-8/#unconstrained-generics-no-longer-assignable-to)

何も判定していないGeneric Typeは、`{}`型に代入できなくなります。

```typescript
function test<T>(t: T) {
  const obj: {} = t; // 今まではOK、これからはNG。
  if (t === null || t === undefined) return;
  const obj2: {} = t; // 今まで通り今後もOK
}
```

## [Decorators are placed on modifiers on TypeScript’s Syntax Trees](https://devblogs.microsoft.com/typescript/announcing-typescript-4-8/#decorators-are-placed-on-modifiers-on-typescripts-syntax-trees)

少し調べてみましたが、そもそもDecoratorの知識があやふやな状態なので分かりませんでした！調べたことをメモしておくので、コメントで補足いただけるとうれしいです。

- JavaScriptでのDecoratorの仕様が固まってきたため、それに合わせてTypeScriptの内部実装を調整する話っぽい。
- `ModifierLike`という型が記事中にあるが、これはTypeScript内部で使う型で、TypeScriptで開発者する人が使う型ではないっぽい。（たぶん）
- 別に今まで通り`export`の前にデコレーターを書いても問題なく使えそう。

## [Binding Patterns Do Not Directly Contribute to Inference Candidates](https://devblogs.microsoft.com/typescript/announcing-typescript-4-8/#binding-patterns-do-not-directly-contribute-to-inference-candidates)

上述の「Improved Inference from Binding Patterns」と同じ内容です。

## その他

上記以外にも項目がありますが、リンクだけまとめます。

- [--build, --watch, and --incremental Performance Improvements](https://devblogs.microsoft.com/typescript/announcing-typescript-4-8/#build-watch-incremental-improvements)
- [File-Watching Fixes (Especially Across git checkouts)](https://devblogs.microsoft.com/typescript/announcing-typescript-4-8/#file-watching-fixes)
- [Find-All-References Performance Improvements](https://devblogs.microsoft.com/typescript/announcing-typescript-4-8/#find-all-refs-improvements)
- [Types Cannot Be Imported/Exported in JavaScript Files](https://devblogs.microsoft.com/typescript/announcing-typescript-4-8/#types-cannot-be-imported-exported-in-javascript-files)
- [Unused Renames in Binding Patterns are Now Errors in Type Signatures](https://devblogs.microsoft.com/typescript/announcing-typescript-4-8/#unused-renames-in-binding-patterns-are-now-errors-in-type-signatures)