TypeScript 1.8

型パラメータの制約

TypeScript 1.8では、型パラメータの制約が、同じ型パラメータリストのものを参照することが可能になります。 以前はこれはエラーになっていました。 この機能は、一般的にF-Bounded Polymorphismと呼ばれます。

function assign<T extends U, U>(target: T, source: U): T {
    for (let id in source) {
        target[id] = source[id];
    }
    return target;
}

let x = { a: 1, b: 2, c: 3, d: 4 };
assign(x, { b: 10, d: 20 });
assign(x, { e: 0 });  // Error

エラーの制御フロー分析

TypeScript 1.8では、ユーザーが遭遇しがちな一般的なエラーを捕捉するのに役立つ制御フロー分析が導入されています。 詳細については、下記のエラー動作を確認してください。

C5ae0f28 7585 11e4 97d8 86169ef2a160

到達不可能(Unreachable)なコード

ランタイム時に実行されないことが確実な文は、到達不可能なコードエラーとして正確にフラグされるようになりました。 例えば、無条件に実行されるreturnthrowbreakcontinue文の後に続く文は、 到達不可能であるとみなされます。 --allowUnreachableCodeを使用すると、到達不可能なコードの検知・報告を無効化することができます。

下記は到達不可能なコードエラーのシンプルな例になります。

function f(x) {
    if (x) {
       return true;
    }
    else {
       return false;
    }

    x = 0; // エラー: 到達不可能なコードを検知
}

この機能が検知する、より一般的なエラーに、return文の後に改行を追加してしまう事例が挙げられます。

function f() {
    return            // 改行を引き金にして、自動的にセミコロンが挿入される
    {
        x: "string"   // エラー: 到達不可能なコードを検知
    }
}

JavaScriptは行末のreturn文で自動的に終了するので、オブジェクトリテラルはブロックになります。

使用されないラベル(Unused labels)

使用されないラベルにもフラグが付くようになりました。 到達不可能なコードのチェックのように、デフォルトで有効になりますが、 --allowUnusedLabelsを使用すると、これらのエラーの報告が無効になります。

loop: while (x > 0) {  // エラー: 使用されないラベル
    x++;
}

暗黙的な戻り値

JavaScriptでは値を返さないコードが書かれた関数は、暗黙的にundefinedを返します。 これらはコンパイラによって暗黙的な戻り値を返すものとしてフラグが立てられるようになります。 デフォルトではこのチェックはOFFになっており、--noImplicitReturnsを使用してONにします。

function f(x) { // Error: Not all code paths return a value.
    if (x) {
        return false;
    }

    // implicitly returns `undefined`
}

抜け落ちるcase

TypeScriptでは、switch文の中でcaseが空では無い且つ抜け落ちる(breakが無い)ような場合に、エラーを報告することができます。 デフォルトではこのチェックはOFFになっており、--noFallthroughCasesInSwitchを使用してONにします。

--noFallthroughCasesInSwitchを使用すると、この例はエラーが発せられます。

switch (x % 2) {
    case 0: // Error: Fallthrough case in switch.
        console.log("even");

    case 1:
        console.log("odd");
        break;
}

ただし下記の例の場合は、抜け落ちるcaseが空であるためエラーは報告されません。

switch (x % 3) {
    case 0:
    case 1:
        console.log("Acceptable");
        break;

    case 2:
        console.log("This is *two much*!");
        break;
}

Reactのステートレス関数コンポーネント

TypeScriptは、ステートレス関数コンポーネント(Stateless Function components)をサポートするようになりました。 これらは、他のコンポーネントを容易に構成することができる軽量コンポーネントです。

// Use parameter destructuring and defaults for easy definition of 'props' type
const Greeter = ({name = 'world'}) => <div>Hello, {name}!</div>;

// Properties get validated
let example = <Greeter name='TypeScript 1.8' />;

この機能と簡略化されたpropsの恩恵を受けるには、react.d.tsの最新版を使用してください。

Reactのpropsの型管理の簡略化

最新のreact.d.ts(1つ前を参照)を使用したTypeScript 1.8では、propsの型の宣言を大幅に簡略化することも行いました。

具体的な内容は下記のとおりです。

  • 明示的なrefkeyの宣言、またはextend React.Propsは必要はありません。
  • refkeyプロパティは、全てのコンポーネントで正しい型として現れます。
  • refプロパティは、ステートレス関数コンポーネントのインスタンス上で、禁止されます。

モジュールのglobal/moduleスコープの拡張

ユーザーは、既存のモジュールに対して自分が作成した機能または誰かが作成した機能を、 拡張することを宣言できるようになりました。 モジュール拡張は、古い素のアンビエントモジュール宣言(つまり、declare module "foo" { }文)のようであり、 自身のモジュールまたは別のアンビエント外部モジュールのトップ階層を直接的に入れ子にします。

さらに、TypeScriptはdeclare global { }形式のグローバルな拡張の概念も持ち合わせています。 これは、必要であればモジュールをArrayのようなグローバルな型に拡張することを可能にしてくれます。

モジュール拡張の名前は、importexport宣言で使用されるモジュール指定と同じルールで解決されます。 モジュール拡張の宣言は、同じファイル内で宣言された場合と同じ方法で既存の宣言とマージされます。

モジュールの拡張もグローバルの拡張も、トップ階層のスコープに新しいアイテムを追加することはできません。 これらは既存の宣言に"パッチを当てる"だけに過ぎません。

ここでmap.tsは、observable.tsからのObservable型の内部にパッチを当て、 mapメソッドの追加を宣言しています。

// observable.ts
export class Observable<T> {
    // ...
}
// map.ts
import { Observable } from "./observable";

// "./observable"の拡張
declare module "./observable" {

    // インターフェースのマージで'Observable'のクラス宣言を拡張
    interface Observable<T> {
        map<U>(proj: (el: T) => U): Observable<U>;
    }

}

Observable.prototype.map = /*...*/;
// consumer.ts
import { Observable } from "./observable";
import "./map";

let o: Observable<number>;
o.map(x => x.toFixed());

同様にグローバルスコープは、declare global宣言を使用してモジュールから拡張することができます。

// Ensure this is treated as a module.
export {};

declare global {
    interface Array<T> {
        mapToNumbers(): number[];
    }
}

Array.prototype.mapToNumbers = function () { /* ... */ }

文字列リテラル型

APIが特定の値に対して特定の文字列を期待するのは珍しいことではありません。 例えば、アニメーション「easing」を制御しながら、 要素が画面を移動するUIライブラリを考えてみましょう。

declare class UIElement {
    animate(options: AnimationOptions): void;
}

interface AnimationOptions {
    deltaX: number;
    deltaY: number;
    easing: string; // "ease-in", "ease-out", "ease-in-out"が指定可能
}

ただし、これは間違いのもとになります。 ユーザーが正しいイージング値を誤って指定したとしても、それを止めるものは何もありません。

// "ease-in-out"とするべきところを"ease-inout"としているがエラーにならない
new UIElement().animate({ deltaX: 100, deltaY: 100, easing: "ease-inout" });

TypeScript 1.8では、文字列リテラル型が導入されました。 これらの型(の値)は文字列リテラルの型の場合と同じように書かれますが、その判定が異なります。

型システムが先程のようなエラーを確実に捕捉し、ユーザーに知らせてくれるようになります。 下記は、文字列リテラル型を使用した新しいAnimationOptionsになります。

interface AnimationOptions {
    deltaX: number;
    deltaY: number;
    easing: "ease-in" | "ease-out" | "ease-in-out";
}

// Error: Type '"ease-inout"' is not assignable to type '"ease-in" | "ease-out" | "ease-in-out"'
new UIElement().animate({ deltaX: 100, deltaY: 100, easing: "ease-inout" });

共用体(union)/交差(intersection)型推論の改善

TypeScript 1.8では、共用体型と交差型の対象の側と被対象の側の両方を含む型推論が改善されています。 例えば、string | string[]string | Tと推論する際に、 string[]Tと推論されることから、string[]の型をTに降格します。

type Maybe<T> = T | void;

function isDefined<T>(x: Maybe<T>): x is T {
    return x !== undefined && x !== null;
}

function isUndefined<T>(x: Maybe<T>): x is void {
    return x === undefined || x === null;
}

function getOrElse<T>(x: Maybe<T>, defaultValue: T): T {
    return isDefined(x) ? x : defaultValue;
}

function test1(x: Maybe<string>) {
    let x1 = getOrElse(x, "Undefined");         // string
    let x2 = isDefined(x) ? x : "Undefined";    // string
    let x3 = isUndefined(x) ? "Undefined" : x;  // string
}

function test2(x: Maybe<number>) {
    let x1 = getOrElse(x, -1);         // number
    let x2 = isDefined(x) ? x : -1;    // number
    let x3 = isUndefined(x) ? -1 : x;  // number
}

--outFileを使用したAMDとSystemモジュールの連携

--outFile--module amdまたは--module systemと一緒に指定すると、 コンパイル時に全てのモジュールが、複数のモジュールクロージャを含む単一の出力ファイルに連結されます。

モジュール名は、各モジュールのrootDirへの相対位置を基準にして導きだされます。

// file src/a.ts
import * as B from "./lib/b";
export function createA() {
    return B.createB();
}
// file src/lib/b.ts
export function createB() {
    return { };
}
結果
define("lib/b", ["require", "exports"], function (require, exports) {
    "use strict";
    function createB() {
        return {};
    }
    exports.createB = createB;
});
define("a", ["require", "exports", "lib/b"], function (require, exports, B) {
    "use strict";
    function createA() {
        return B.createB();
    }
    exports.createA = createA;
});

SystemJSを使用したdefault importの相互運用のサポート

SystemJSのようなモジュールローダーはCommonJSモジュールをラップし、 defaultのES6のimportとして公開します。

これにより、ローダーに基づくモジュールの形状が異なるものになるため、 SystemJSとCommonJSのモジュール実装の定義ファイルの共有が不可能になります。

新しいコンパイラフラグである--allowSyntheticDefaultImportsを設定すると、 モジュールローダーがインポートされた.tsまたは.d.ts内で示されていない、何らかの合成(Synthetic)default importを実行します。

コンパイラは、モジュール全体の形状を持つdefaultのエクスポートの存在を推論します。

システムモジュールはデフォルトでこのフラグをオンにします。

ループ内のlet/constの補足の改善

以前はエラーになっていましたが、TypeScript 1.8でサポートされるようになりました。 ループ内と関数内で補足されるlet/const宣言は、 意味的に正しくマッチするように出力されるようになりました。

let list = [];
for (let i = 0; i < 5; i++) {
    list.push(() => i);
}

list.forEach(f => console.log(f()));

上記は下記のようにコンパイルされます。

var list = [];
var _loop_1 = function(i) {
    list.push(function () { return i; });
};
for (var i = 0; i < 5; i++) {
    _loop_1(i);
}
list.forEach(function (f) { return console.log(f()); });

結果は次のようになります。

0
1
2
3
4

for..in文チェックの改善

以前は、for..inの変数の型をanyと推論していたため、 for..inの本文内での不正な使われ方がコンパイラによって許可されていました。

TypeScript 1.8からは、

  • for..in文の中で定義された変数の型は、暗黙的に文字列となります。
  • Tの数値インデックスのシグネチャを持つオブジェクト(配列のような)が、 文字列のインデックスのシグネチャではなく、数値のインデックスのシグネチャを持つオブジェクトのために、 for..in文に含まれるfor..inの変数によってインデックスされる場合(再度配列のように)、 型Tの値が作られます。
var a: MyObject[];
for (var x in a) {   // xの型は暗黙的に文字列になります
    var obj = a[x];  // objの型はMyObjectです
}

モジュールによる"use strict"の文頭出力

モジュールはES6を対象とする場合には常にstrictモードで解析されていましたが、 非ES6を対象とする場合は生成されたコード内でこれが尊重されていませんでした。 TypeScript 1.8からは、生成されたモジュールは常にstrictモードになります。

これはTSがほとんどのstrictモードのエラーをコンパイル時のエラーとみなすため、 ほぼ全てのコードで目に見える変化を確認することは出来ないでしょう。 ただし、NaNの割り当てのような、TSコードで内でランタイム時に沈黙的に失敗する幾つかの事が、 騒々しく失敗するようになります。

strictモードと非strictモードの違いの詳細なリストを、 MDNの記事で参照することができます。

--allowJsに.jsファイルを含める

プロジェクトには、TypeScriptで作成できない外部ソースファイルが存在することがよくあります。

あるいは、JSベースのコードをTSに変換しようとしているものの、 新しいTSコードの出力と一緒に、全てのJSコードを単一のファイルにまとめたいというニーズがあるかもしれません。(翻訳に自信なし)

.jsファイルは、tscへの入力対象として許可されるようになりました。 TypeScriptコンパイラは入力対象の.jsファイルの構文エラーをチェックし、 --target--moduleフラグを基にして正しく出力処理を実行します。

出力は他の.tsファイルと組み合わせることもできます。 ソースマップは.tsファイルの場合と同様に、.jsファイル用に生成されます。

--reactNamespaceを使用したカスタムJSXファクトリー

--jsx reactと一緒に--reactNamespace <JSX factory Name>を渡すことで、 そのデフォルトのReactとは異なるJSXファクトリーが使用できるようになります。 新しいファクトリー名は、createElement__spread関数の呼び出しに使用されます。

import {jsxFactory} from "jsxFactory";

var div = <div>Hello JSX!</div>

これを次のようにコンパイルすると、

tsc --jsx react --reactNamespace jsxFactory --m commonJS

結果は次のようになります。

"use strict";
var jsxFactory_1 = require("jsxFactory");
var div = jsxFactory_1.jsxFactory.createElement("div", null, "Hello JSX!");

thisを基にした型の保護

TypeScript 1.8は、ユーザー定義の型の保護(type guard)関数を、クラスとインターフェースメソッドへ拡張できるようになります。

this is Tはクラスとインターフェース内のメソッドで、正当な戻り値の型アノテーションになります。 型が絞り込まれる位置(if文など)で使用されると、呼び出し式の対象オブジェクトの型がTに絞り込まれます。

class FileSystemObject {
    isFile(): this is File { return this instanceof File; }
    isDirectory(): this is Directory { return this instanceof Directory;}
    isNetworked(): this is (Networked & this) { return this.networked; }
    constructor(public path: string, private networked: boolean) {}
}

class File extends FileSystemObject {
    constructor(path: string, public content: string) { super(path, false); }
}
class Directory extends FileSystemObject {
    children: FileSystemObject[];
}
interface Networked {
    host: string;
}

let fso: FileSystemObject = new File("foo/bar.txt", "foo");
if (fso.isFile()) {
    fso.content; // fso is File
}
else if (fso.isDirectory()) {
    fso.children; // fso is Directory
}
else if (fso.isNetworked()) {
    fso.host; // fso is networked
}

公式のTypeSriptのNuGetパッケージ

TypScript 1.8からは、Typescriptコンパイラ(tsc.exe)とMSBuildインテグレーション(Microsoft.TypeScript.targetsおよびMicrosoft.TypeScript.Tasks.dll)で、 公式のNuGetパッケージが使用できるようになりました。

Stableのパッケージで使用できるものは下記のとおりです。

また、nightlyのnpmパッケージと一致するnightlyのNuGetパッケージは、 mygetで利用できます。

tscのエラーメッセージの装飾

我々は、大量のモノクロ出力が目に優しくないことは重々承知しています。 色を付けることでメッセージの開始と終了の識別を容易にし、 大量のエラー出力があった場合に、この視覚的な手がかりが重要になってきます。

--prettyコマンドラインオプションを渡すだけで、 TypeScriptはどこに誤りがあるのかを色付きで教えてくれます。

Pretty01

Visual Studio 2015によるJSXコードの色付け

TypeScript 1.8では、Visual Studio 2015でJSXタグが、分類・色付されるようになりました。

B875c502 b90f 11e5 93d8 c6740be354d1

この分類は、Tools->Options->Environment->Fonts and Colorsのページで、 VB XML色とフォント設定の、フォントと色の設定を変更することで更にカスタマイズすることができます。

--project (-p)フラグのファイルパスの受け入れ

--projectコマンドラインオプションは、もともとtsconfig.jsonを含むフォルダへのパスしか取れませんでした。 ビルド設定に異なるシナリオを与えると、--projectは他の互換性のあるJSONファイルを指すことができるようになりました。

例えば、ユーザーがNode5のCommonJSモジュールではES2015をターゲットにしたいが、 ブラウザ用のAMDモジュールではES5をターゲットにしたいケースがあるかもしれません。 この新しい動作により、tsconfig.jsonファイルを別のディレクトリに配置するといった回避策やハックを行うことなく、 tscだけを使用して2つの別々のビルドターゲットを簡単に管理できるようになりました。

ディレクトリが指定されても、元の古い動作は変わりません。 コンパイラは、tsconfig.jsonという名前のファイルをそのディレクトリ内で見つけようとします。

tsconfig.json内へのコメント

設定にドキュメントを残せるということは、どんな場合でも歓迎されることでしょう。 tsconfig.jsonに、単一行・複数行のコメントが記述できるようになりました。

{
    "compilerOptions": {
        "target": "ES2015", // running on node v5, yaay!
        "sourceMap": true   // makes debugging easier
    },
    /*
     * Excluded files
      */
    "exclude": [
        "file.d.ts"
    ]
}

IPC-drivenファイルへの出力のサポート

TypeScript 1.8では、ユーザーが--outFile引数を、 名前付きパイプやデバイスなどの特殊なファイルシステムのエンティティで使用できるようになりました。

一例として、多くのUnixライクなシステムでは、標準出力のストリームは/dev/stdoutファイルからアクセスできます。

tsc foo.ts --outFile /dev/stdout

これはコマンド間で、出力をパイプするのにも使用することができます。

例えば、生成したJavaSccriptをpretty-jsのようなプログラム整形器(pretty printer)にパイプすることができます。

tsc foo.ts --outFile /dev/stdout | pretty-js

Visual Studio 2015のtsconfig.jsonサポートの改善

TypeScript 1.8では、tsconfig.jsonファイルが全てのタイプのプロジェクトで使用できるようになります。 これには、TypeScriptを使用したASP.NET v4プロジェクト、コンソールアプリケーション、HTMLアプリケーションのプロジェクトのタイプが含まれます。

更にひとつのtsconfig.jsonファイルに縛られることなく、 複数のファイルを追加して、各プロジェクト毎にそれぞれのファイルを構築することが可能です。 これにより、複数の異なるプロジェクトを持つこと無く、アプリケーションのさまざまな部分の構成を分離することが可能になります。

Tsconfig in vs

また、tsconfig.jsonファイルが追加されると、プロジェクトのプロパティページを無効にするようにしました。 これは、構成の変更が必要であればtsconfig.jsonファイル自体にそれを行う必要があることを意味します。

いくつかの制限

  • tsconfig.jsonファイルを追加すると、そのコンテキストの一部とみなされないTypeScriptファイルはコンパイルされません。
  • Apache Cordovaアプリケーションには、依然としてルートまたはscriptsフォルダーのいずれかに、 単一のtsconfig.jsonファイルを配置しなければいけない制限があります。
  • ほとんどのプロジェクトのタイプに、tsconfig.jsonのためのテンプレートが存在しません。

 Back to top

© https://github.com/Microsoft/TypeScript-Handbook

このページは、ページトップのリンク先のTypeScript-Handbook内のページを翻訳した内容を基に構成されています。 下記の項目を確認し、必要に応じて公式のドキュメントをご確認ください。 もし、誤訳などの間違いを見つけましたら、 @tomofまで教えていただければ幸いです。

  • ドキュメントの情報が古い可能性があります。
  • "訳注:"などの断わりを入れた上で、日本人向けの情報やより分かり易くするための追記を行っている事があります。