プログラミング言語「TypeScript 4.3」をMicrosoftが公開インポート文の入力補完が利用可能に

Microsoftはオープンソースのプログラミング言語の最新版「TypeScript 4.3」を公開した。多くの機能が追加、強化され、パフォーマンスも向上している。

» 2021年05月31日 16時30分 公開
[@IT]

この記事は会員限定です。会員登録(無料)すると全てご覧いただけます。

 Microsoftは2021年5月26日(米国時間)、オープンソースのプログラミング言語の最新版「TypeScript 4.3」を公開した。多くの新しい機能の導入や機能改善が行われ、生産性やパフォーマンスも向上している。

 TypeScriptは静的型付けができる言語であり、JavaScriptのスーパーセットだ。ECMA規格に従った最新のJavaScriptの機能を、古いWebブラウザやランタイムが扱えるようにコンパイルすることもできる。

 NuGetを使うか、次のコマンドラインのように、npmを使ってTypeScript 4.3をインストールできる。

npm install typescript

 TypeScript 4.3は「Visual Studio 2019」と「Visual Studio 2017」のエディタでサポートされており、「Visual Studio Code」(以下、VS Code)、「Sublime Text 3」でも利用できる。TypeScript 4.3の主な特徴は次の通り。

getterとsetterに別の型を指定可能に

 プロパティの読み取りと書き込みの型を指定できるようになり、getterとsetterのペアが異なる型を取れるようになった。これらの型はクラスやオブジェクトリテラル、インタフェース、オブジェクト型で別々に指定できる。

const thing = {
    _size: 0,
    get size(): number {
        return this._size;
    },
    set size(value: string | number) {
        this._size = typeof value === 'string' ? parseInt(value) : value;
    }
}
// OK
thing.size = "100ish";
// OK
console.log(thing.size.toFixed(2));

 この新機能には、「getterの型はsetterに割り当て可能でなければならない」という制限がある。これにより、一定の一貫性が確保され、プロパティは常に、自身への割り当てが可能になる。

「override」キーワードと「--noImplicitOverride」フラグが利用可能に

 「override」キーワードと「--noImplicitOverride」フラグが導入された。overrideキーワードは、クラスメソッドが基底クラスのメソッドをオーバーライドすることを示す。--noImplicitOverrideフラグが有効になっている場合、明示的にoverrideキーワードを使っていない限り、スーパークラスのメソッドをオーバーライドすると、エラーになる。

 次のサンプルでは、--noImplicitOverrideフラグが有効になっていると、エラーになる。これは、「Derived」内のメソッドの名前を変える必要があることを示唆している。

class Base {
    someHelperMethod() {
        // ...
    }
}
class Derived extends Base {
    // Oops! We weren't trying to override here,
    // we just needed to write a local helper method.
    someHelperMethod() {
        // ...
    }
}

テンプレートリテラル型同士の関係と推論をサポート

 テンプレートリテラル型同士の関係と推論がサポートされた。型関係のターゲット側がテンプレートリテラル型である場合、ソース側は互換性のあるテンプレートリテラル型であることが可能だ。同様に、テンプレートリテラルターゲット型に推論する場合、ソース型もテンプレートリテラル型であることが可能だ。

 改善された代入関係の例を次に示す。

declare let s1: `${number}-${number}-${number}`;
declare let s2: `1-2-3`;
declare let s3: `${number}-2-3`;
declare let s4: `1-${number}-3`;
declare let s5: `1-2-${number}`;
declare let s6: `${number}-2-${number}`;
s1 = s2;
s1 = s3;
s1 = s4;
s1 = s5;
s1 = s6;

 これまでは最初の代入だけが可能だったが、例にある全ての代入が可能になった。

 テンプレートリテラル型同士の推論の例を次に示す。

declare function foo1<V extends string>(arg: `*${V}*`): V;
function test<T extends string>(s: string, n: number, b: boolean, t: T) {
    let x1 = foo('*hello*');  // "hello"
    let x2 = foo('**hello**');  // "*hello*"
    let x3 = foo(`*${s}*` as const);  // string
    let x4 = foo(`*${n}*` as const);  // `${number}`
    let x5 = foo(`*${b}*` as const);  // "true" | "false"
    let x6 = foo(`*${t}*` as const);  // `${T}`
    let x7 = foo(`**${s}**` as const);  // `*${string}*`
}

コンテキストで型付けされたテンプレートリテラル式がテンプレートリテラル型に

 コンテキストで型付けされたテンプレートリテラル式のためにテンプレートリテラル型が導入された。具体的には次のような場合に、テンプレートリテラル式にテンプレートリテラル型が与えられる。

  • テンプレートリテラル式が文字列リテラル型またはテンプレートリテラル型によってコンテキストで型付けされている場合
  • テンプレートリテラル式がstring型に割り当て可能な制約を持つ汎用(はんよう)型によってコンテキストで型付けされ、望ましいのはテンプレートリテラル型だと明らかなシナリオで、as constを使用する必要性を軽減する場合

 この抑制的なアプローチにより、破壊的変更が発生せず、下位互換性が確保される。次に例を示す。

function bar(s: string): `hello ${string}` {
    return `hello ${s}`;  // Now ok, previously was error
}
declare let s: string;
declare function g1<T>(x: T): T;
declare function g2<T extends string>(x: T): T;
let x1 = g1(`xyz-${s}`);  // string
let x2 = g2(`xyz-${s}`);  // `xyz-${string}`, previously was string

ECMAScriptの#privateクラス要素が拡大

 #private #namesを与えて、実行時に真にプライベートにすることができるクラス要素の種類が広がった。プロパティに加えて、メソッドとアクセサ(getter、setter)にもプライベート名を付けることが可能だ。

class Foo {
    #someMethod() {
        //...
    }
    get #someValue() {
        return 100;
    }
    publicMethod() {
        // These work.
        // We can access private-named members inside this class.
        this.#someMethod();
        return this.#someValue;
    }
}
new Foo().#someMethod();
//        ~~~~~~~~~~~
// error!
// Property '#someMethod' is not accessible
// outside class 'Foo' because it has a private identifier.
new Foo().#someValue;
//        ~~~~~~~~~~
// error!
// Property '#someValue' is not accessible
// outside class 'Foo' because it has a private identifier.

 さらに、静的メンバーもプライベート名を持てるようになった。

class Foo {
    static #someMethod() {
        // ...
    }
}
Foo.#someMethod();
//  ~~~~~~~~~~~
// error!
// Property '#someMethod' is not accessible
// outside class 'Foo' because it has a private identifier.

制御フロー分析における汎用型の絞り込みが改善

 参照の型がunion型制約を持つ汎用型である場合や、この汎用型を含む場合、あるいは参照が制約のある場所にある場合や、汎用型を含まないコンテキスト型を持つ(制約が割り当て可能性を決定する)場合、参照の型における全ての汎用型の代わりに制約を使用し、制御フロー分析によって、それをさらに絞り込めるようになった。例えば、次のようになる。

function f1<T extends string | undefined>(x: T): string {
    if (x) {
        x.length;  // x narrowed to string
        return x;  // x narrowed to string (error before this PR)
    }
}

 上の「x.length」では、「x」の型における「T」の代わりに「string | undefined」を使用している。「x」が制約のある場所にあるからだ。

 同様に「return x」では、「x」の型における「T」の代わりに、「string | undefined」を使用している。「x」が汎用型を含まないコンテキスト型「string」を持つからだ。続いて、制御フロー分析によって、含まれるif文の真偽チェックを基に、「string | undefined」を「string」に絞り込める。

 追加の例を示す。

declare function takeA(a: 'a'): void;
declare function takeB(b: 'b'); void;
function f2<T extends 'a' | 'b'>(x: T) {
    if (x === 'a') {
        takeA(x);  // x narrowed to 'a' (error before this PR)
    }
    else {
        takeB(x);  // x narrowed to 'b' (error before this PR)
    }
}
type A = { kind: 'a', value: string };
type B = { kind: 'b', value: number };
function f3<T extends A | B>(x: T) {
    if (x.kind === 'a') {
        x.value;  // Narrowed to string (wasn't narrowed before this PR)
    }
    else {
        x.value;  // Narrowed to number (wasn't narrowed before this PR)
    }
}

staticインデックスシグネチャをサポート

 インデックスシグネチャにより、型の明示的宣言を用いて多くのプロパティを値に設定できる。

class Foo {
    hello = "hello";
    world = 1234;
    // This is an index signature:
    [propName: string]: string | number | undefined;
}
let instance = new Foo();
// Valid assigment
instance["whatever"] = 42;
// Has type 'string | number | undefined'.
let x = instance["something"];

 インデックスシグネチャはこれまで、クラスのインスタンス側でしか宣言できなかったが、staticとして宣言できるようになった。

class Foo {
    static hello = "hello";
    static world = 1234;
    static [propName: string]: string | number | undefined;
}
// Valid.
Foo["whatever"] = 42;
// Has type 'string | number | undefined'
let x = Foo["something"];

 クラスの静的側のインデックスシグネチャには、インスタンス側と同じルールが適用される。つまり、他の全ての静的プロパティは、インデックスシグネチャと互換性がなければならない。

class Foo {
    static prop = true;
    //     ~~~~
    // Error! Property 'prop' of type 'boolean'
    // is not assignable to string index type
    // 'string | number | undefined'.
    static [propName: string]: string | number | undefined;
}

インポート文の入力補完が利用可能に

 JavaScriptのインポート文とエクスポート文でユーザーが直面する難点の一つは、記述の順序だ。具体的には、インポートの記述が次のようになってしまう。

import { func } from "./module.js";
 次のような記述であればよいのだが、そうではない。
from "./module.js" import { func };

 そのため、インポート文を一から書き始めると、入力補完が適切に働かない。例えば、「import {,」と書き始めると、TypeScriptは、ユーザーがどのモジュールからインポートしようとしているか分からないため、補完による絞り込みが機能しない。

 TypeScript 4.3では、パスのないimport文を書き始めると、インポートの候補リストが表示され、いずれかを選ぶと、パスを含めて文全体が補完される。

import文の入力補完(出典:Microsoft、クリックで再生)

 ただし制限もある。この機能をサポートするエディタでしか利用できないことだ。例えば、VS Codeの最新Insiders版では試すことが可能だ。

@linkタグのエディタサポート

 TypeScriptは「@link」タグを理解し、このタグのリンク先の宣言を解決しようとするようになった。@linkタグ内の名前にカーソルをホバー動作して、素早く情報を入手したり、「go-to-definition」や「find-all-references」のようなコマンドを使ったりできる。

 次の例では、「@link plantCarrot」内の「plantCarrot」にホバー動作してgo-to-definitionを選ぶと、TypeScriptに対応したエディタは、plantCarrotの関数定義に移動する。

/**
 * To be called 70 to 80 days after {@link plantCarrot}.
 */
function harvestCarrot(carrot: Carrot) {
}
/**
 * Call early in spring for best results. Added in v2.1.0.
 * @param seed Make sure it's a carrot seed!
 */
function plantCarrot(seed: Seed) {
    // TODO: some gardening
}
plantCarrotの関数定義に移動する動作(出典:Microsoft、クリックで再生)

JavaScript以外のファイルのパスでの「定義に移動」機能が改善

 TypeScriptの言語サービスは、相対ファイルパス上のファイルがJavaScriptファイルやTypeScriptファイルでなくても、ユーザーが相対ファイルパスでgo-to-definitionを選択すると、正しいファイルに移動しようとするようになった。

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。