TypeScript 4.2 の機能について

TypeScript 4.2 の機能についてまとめます。

概要

TypeScript 4.2 が 2021年2月23日にリリースされました。 TypeScript 4.2 では主に下記の機能が実装されました。

  • Smarter Type Alias Preservation
  • Leading/Middle Rest Elements in Tuple Types
  • Stricter Checks For The in Operator
  • --noPropertyAccessFromIndexSignature
  • abstract Construct Signatures
  • The --explainFiles Flag
  • Improved Uncalled Function Checks in Logical Expressions
  • Destructured Variables Can Be Explicitly Marked as Unused
  • Relaxed Rules Between Optional Properties and String Index Signatures
  • Declare Missing Helper Function
  • Breaking Changes

これらの機能について順に説明してきます。

Smarter Type Alias Preservation

名前の通り Type Alias の機能がより賢くなりました。

では バージョン4.2 より前の TypeScript ではどのように賢くなかったのか見てみましょう。

TypeScript を使用しているとき、 複数型を共用型で書くときに再利用のため Type Alias を使用すると思います。

type BasicPrimitive = number | string | boolean;

例えばこの型を使用し以下のような関数を定義したとします。

export type BasicPrimitive = number | string | boolean; export function doStuff(value: BasicPrimitive) { let x = value; return x; }

この時 VSCode や Playground上で x 変数をホバーし型を見てみると BasicPrimitive と表示され、正しく推論されていることが分かります。

ここで少し修正を加えてみます。

export type BasicPrimitive = number | string | boolean; export function doStuff(value: BasicPrimitive) { if (Math.random() < 0.5) { return undefined; } return value; }

今度は条件文を加え undefined を返すパターンを加えました。

この時も同様に value をホバーしてみると今度は string | number | boolean | undefined と表示されてしまいます。

これは意図していない型です。

なぜこのようなことが起きてしまったのでしょうか?

実は TypeScriptの内部では一つ以上の共用型で構成される共用型のとき、これらを1次元の共用型に変換しています。 この場合だと

BasicPrimitiv | undefined

(number | string | boolean) | undefined

となり

number | string | boolean| undefined

といった感じでネストが外れます。

このような処理を行った後で型検査では、この型のすべての組み合わせから value の型を推測しようとするのですが、もとの型情報を失っているため意図する型を推測することができなくなっているのです。

そこで TypeScript 4.2 では少し賢くなり、このネストを外す処理の前の型情報を保持しておくことで、 意図した型 (この場合は BasicPrimitive) を推論できるようになりました。

Leading/Middle Rest Elements in Tuple Types

TypeScriptのタプル型は配列 [] の記法を使用し記述することができます。 タプル型はパラメータ引数と同様の機能を持つよう成長してきました。

例えば下記のようにオプショナルな要素やレストな要素が使用することができます。

let c: [string, string?] = ["hello"]; c = ["hello", "world"]; let d: [first: string, second?: string] = ["hello"]; d = ["hello", "world"]; let e: [string, string, ...boolean[]]; e = ["hello", "world"]; e = ["hello", "world", false]; e = ["hello", "world", true, false, true];

以前のバージョンではレストな要素は末尾に記述する必要がありました。

しかしTypeScript 4.2 ではこの制約がなくなり、どこにでも書くことができるようになりました。

let foo: [...string[], number]; foo = [123]; foo = ["hello", 123]; foo = ["hello!", "hello!", "hello!", 123]; let bar: [boolean, ...string[], boolean]; bar = [true, false]; bar = [true, "some text", false]; bar = [true, "some", "separated", "text", false];

しかしながらまだ制約が残っており、複数のレストの要素が存在している、またはレストとオプショナルな要素が混在しているタプルは記述することができません。

interface Clown { /*...*/ } interface Joker { /*...*/ } let StealersWheel: [...Clown[], "me", ...Joker[]]; // ~~~~~~~~~~ Error! let StringsAndMaybeBoolean: [...string[], boolean?]; // ~~~~~~~~ Error!

こんかい新しくなったタプル型を使用して、下記のような関数を作成することができます。

// 末尾の引数は boolean で、それより前の引数は string declare function doStuff(...args: [...names: string[], shouldCapitalize: boolean]): void; doStuff(/*shouldCapitalize:*/ false) doStuff("fee", "fi", "fo", "fum", /*shouldCapitalize:*/ true);

Stricter Checks For The in Operator

--noPropertyAccessFromIndexSignature

インデックス型がTypeScriptへ導入された当初、値へアクセスするためには ブランケット表記法にする必要がありました。

interface SomeType { /** インデックス型 */ [propName: string]: any; } function doStuff(value: SomeType) { let x = value["someProperty"]; }

この制約が原因で少々困った問題がよく発生しました。 たとえば

interface Options { /** 除外するファイルのパターン. */ exclude?: string[]; /** * その他のオプション */ [x: string]: any; }

という Option 型があったときに、 exclude で除外するファイルのパターンを指定したいところ、 誤って複数形の excludes と書いてしまう場面があると思います。 それを防ぐために、エラーメッセージを吐き出そうと下記のようなコードを書きました。

function processOptions(opts: Options) { //ここでは意図的に excludes と書いている if (opts.excludes) { console.error("The option `excludes` is not valid. Did you mean `exclude`?"); } }

しかし、インデックス型はブランケット表記法でしかアクセスができないので上記のコードではエラーが発生してしまいます。 また既存のJavaScriptのコードをTypeScriptへ変換するときにも、わざわざブランケット表記法へ変換する手間が発生してしまいます。

このような問題を解決するためTypeScriptではドット表記法でもアクセスできるようになりました。

しかしこのように制約を緩めたことによって下記のような誤ったコードも、エラーを吐かずに実行できてしまうため 問題に気付かない可能性が高まってしまいます。

function processOptions(opts: Options) { // 本来 excludeなのだが。。 for (const excludePattern of opts.excludes) { // ... } }

そこで TypeScript 4.2 では敢えて昔の制約 (ブランケット表記法でしかアクセスできない)に戻せるように --noPropertyAccessFromIndexSignatur フラグが追加されました。

abstract Construct Signatures

The --explainFiles Flag

Improved Uncalled Function Checks in Logical Expressions

TypeScrkpt4.2 から呼び出されていない関数が &&|| を適用されてた時に、エラーチェックをするようになりました。

--strictNullChecks を適用した後、下記のようなコードでエラーを検出するようになります。

function shouldDisplayElement(element: Element) { // ... return true; } function getVisibleItems(elements: Element[]) { return elements.filter(e => shouldDisplayElement && e.children.length) // ~~~~~~~~~~~~~~~~~~~~ // この場合 関数オブジェクト shouldDisplayElement は常に trueになるためエラーになる }

Destructured Variables Can Be Explicitly Marked as Unused

分割代入をしたときに使用していない変数に対し _ の接頭辞を付けることで 以前のバージョンで発生していた noUnusedLocal を 避けることができます。

let [_first, second] = getValues();

Relaxed Rules Between Optional Properties and String Index Signatures

Declare Missing Helper Function

Breaking Changes

参考元

https://devblogs.microsoft.com/typescript/announcing-typescript-4-2/

©Tsurutan. All Rights Reserved.