検索
ニュース

TypeScript 5.5 β版公開、「型述語の推論」や「JSDoc内の型インポート」とは最終リリース版は2024年6月公開予定

Microsoftは「TypeScript 5.5」のβ版を公開した。本記事では、追加された「型述語の推論」と「JSDoc内の型インポート」について取り上げる。

Share
Tweet
LINE
Hatena

 Microsoftは2024年4月25日(米国時間、以下同)、オープンソースのプログラミング言語の最新版「TypeScript 5.5」のβ版を公開した。

 TypeScript 5.5は2024年5月時点「feature-stable」の段階にある。2024年6月4日にRC(リリース候補)版が、6月18日に最終リリース版が公開予定だ。

 β版は、「NuGet」か以下のコマンドでnpmを通じて入手できる。

npm install -D typescript@beta

 本記事ではTypeScript 5.5に実装された主要な機能「型述語の推論」「JSDoc内の型インポート」について紹介する。

型述語の推論

 TypeScriptの制御フロー分析は、変数がコード内を移動する際にその型がどのように変化するかを追跡する機能を有している。

interface Bird {
    commonName: string;
    scientificName: string;
    sing(): void;
}
// Maps country names -> national bird.
// Not all nations have official birds (looking at you, Canada!)
declare const nationalBirds: Map<string, Bird>;
function makeNationalBirdCall(country: string) {
  const bird = nationalBirds.get(country);  // bird has a declared type of Bird | undefined
  if (bird) {
    bird.sing();  // bird has type Bird inside the if statement
  } else {
    // bird has type undefined here.
  }
}

 TypeScriptでは、上記コードのように開発者がコード内で「undefined」のケースに対処することで、より堅牢(けんろう)なコードを作成するように促している。

 5.5より前のバージョンでは、こういった開発者による型の絞り込みを配列に適用するのは困難だった。例えば、下記のような記述はエラーになった。

function makeBirdCalls(countries: string[]) {
  // birds: (Bird | undefined)[]
  const birds = countries
    .map(country => nationalBirds.get(country))
    .filter(bird => bird !== undefined);
  for (const bird of birds) {
    bird.sing();  // error: 'bird' is possibly 'undefined'.
  }
}

 リストから「undefined」の値を全てフィルタリングしており、このコード自体に問題はない。しかし、この場合「filter」が型チェッカーとして動作していなかった。

 TypeScript 5.5では、下記コードでも型チェッカーとして問題なく動作するようになった。

function makeBirdCalls(countries: string[]) {
  // birds: Bird[]
  const birds = countries
    .map(country => nationalBirds.get(country))
    .filter(bird => bird !== undefined);
  for (const bird of birds) {
    bird.sing();  // ok!
  }
}

 また、TypeScript5.5では「filter」関数の型述語を推論するようになった。これを独立した関数に取り出すことで、何が起こっているのかをより明確に見ることができる。

// function isBirdReal(bird: Bird | undefined): bird is Bird
function isBirdReal(bird: Bird | undefined) {
  return bird !== undefined;
}

 上述の例では、「bird is Bird」が型述語だ。これは、関数が「true」を返す場合、引数が「Bird」型であるということを示している。関数が「false」を返す場合、引数が「undefined」であるということを示している。

 「Array.prototype.filter」の型宣言が型述語を認識するので、開発者は型述語を書かずにコーティングできる。

 TypeScript 5.5で型述語の推論が適用される条件は以下の通り。

  1. 関数に明示的な戻り値の型や型述語の注釈がないこと
  2. 関数が単一のreturn文を持ち、暗黙のreturnがないこと
  3. 関数がパラメーターを変更しないこと
  4. 関数が、パラメーターの絞り込みに関連付けられたブール式を返すこと

JSDoc内の型インポート

 これまでのTypeScriptでは、JavaScriptファイルで型チェックのためだけに何かをインポートしようとすると、手間がかかっていた。下の例では、「SomeType」という名前の型が実行時に存在しない場合、JavaScript開発者はこれを単純にインポートすることができなかった。

// ./some-module.d.ts
export interface SomeType {
    // ...
}
// ./index.js
import { SomeType } from "./some-module"; //  runtime error!
/**
 * @param {SomeType} myValue
 */
function doSomething(myValue) {
    // ...
}

 SomeTypeは実行時に存在しないため、インポートは失敗する。開発者はその代わりに下記のように名前空間としてインポートする方法を取ることができる。

import * as someModule from "./some-module";
/**
 * @param {someModule.SomeType} myValue
 */
function doSomething(myValue) {
    // ...
}

 ただし、「./some-module」は実行時にインポートされてしまう。これも開発者にとって不必要なケースがある。

 これを避けるために、開発者はJSDocコメント内でimport(...)型を使用する必要があった。

/**
 * @param {import("./some-module").SomeType} myValue
 */
function doSomething(myValue) {
    // ...
}

 同じ型を複数の場所で再利用したい場合は、「typedef」を使用して、インポートの繰り返しを避けていた。

/**
 * @typedef {import("./some-module").SomeType} SomeType
 */
/**
 * @param {SomeType} myValue
 */
function doSomething(myValue) {
    // ...
}

 これは「SomeType」をローカルで使用する場合には役立つが、多くのインポートがある場合、繰り返しが発生し冗長になる可能性がある。

 TypeScript 5.5では、ECMAScriptのインポートと同じ構文を持つ新しい@importコメントタグをサポートするようになった。記述例は下記の通り。

/** @import { SomeType } from "some-module" */
/**
 * @param {SomeType} myValue
 */
function doSomething(myValue) {
    // ...
}

 インポートを名前空間インポートとして記述することもできる。

/** @import * as someModule from "some-module" */
/**
 * @param {someModule.SomeType} myValue
 */
function doSomething(myValue) {
    // ...
}

Copyright © ITmedia, Inc. All Rights Reserved.

ページトップに戻る