ここでは、.js
ファイルを.ts
ファイルと比較したときの、チェック機能の違いについて注目すべき点をいくつか挙げます。
プロパティはクラス本体における代入から推測される
ES2015には、クラスのプロパティを宣言する手段がありません。プロパティはオブジェクトリテラルのように、動的に代入されます。
.js
ファイルでは、コンパイラはクラス本体のプロパティの代入からプロパティを推測します。
コンストラクタで型が定義されていない場合や、コンストラクタでの型がundefinedまたはnullである場合を除いて、プロパティの型はコンストラクタ内で与えられた型になります。
プロパティの型がコンストラクタ内で与えられない場合、プロパティの型は、プロパティの代入式におけるすべての右辺の値の型によるUnion型となります。
メソッドやgetter、setter内で定義されたプロパティは任意とみなされるのに対して、コンストラクタで定義されたプロパティは常に存在するものとみなされます。
jsTry
classC {constructor() {this.constructorOnly = 0;this.constructorUnknown =undefined ;}method () {this.Type 'boolean' is not assignable to type 'number'.2322Type 'boolean' is not assignable to type 'number'.constructorOnly = false;this.constructorUnknown = "plunkbat"; // ok、constructorUnknown は string | undefined ですthis.methodOnly = "ok"; // ok、しかし、methodOnlyはundefinedの可能性もあります}method2 () {this.methodOnly = true; // こちらもokです。methodOnlyの型は string | boolean | undefined です}}
クラス本体でプロパティが設定されていない場合、そのプロパティは未知のものとみなされます。 クラスプロパティが読み取り専用ならば、コンストラクタにプロパティを追加した後に、型指定のためJSDocを使って型宣言の注釈を付けます。 後で初期化されるのであれば、値を指定する必要さえありません:
jsTry
classC {constructor() {/** @type {number | undefined} */this.prop =undefined ;/** @type {number | undefined} */this.count ;}}letc = newC ();c .prop = 0; // OKType 'string' is not assignable to type 'number'.2322Type 'string' is not assignable to type 'number'.c .count = "string";
コンストラクタ関数はクラスと同等である
ES2015以前のJavascriptでは、クラスの代わりにコンストラクタ関数が使われていました。 コンパイラはこうしたパターンをサポートしており、コンストラクタ関数をES2015のクラスと同等のものとして理解します。 上記で説明したプロパティの推論ルールも全く同じように動作します。
jsTry
functionC () {this.constructorOnly = 0;this.constructorUnknown =undefined ;}C .prototype .method = function () {this.Type 'boolean' is not assignable to type 'number'.2322Type 'boolean' is not assignable to type 'number'.constructorOnly = false;this.constructorUnknown = "plunkbat"; // OK、型はstring | undefinedです};
CommonJSモジュールがサポートされている
.js
ファイルでは、TypeScriptはCommonJSモジュール形式をサポートしています。
exports
やmodule.exports
への代入は、エクスポート宣言として認識されていますし、
同様にrequire
関数の呼び出しも、モジュールインポートとして認識されます。例えば:
js
// `import module "fs"`と同じconst fs = require("fs");// `export function readFile`と同じmodule.exports.readFile = function (f) {return fs.readFileSync(f);};
JavaScriptのモジュールサポートは、TypeScriptのモジュールサポートよりも構文的に寛容です。 ほとんどの代入と宣言の組み合わせがサポートされています。
クラス、関数、オブジェクトリテラルは名前空間を作る
クラスは.js
ファイルにおける名前空間を作成します。
これを利用してクラスをネストすることができます。例えば:
jsTry
classC {}C .D = class {};
また、ES2015以前のコードのための擬似的な静的メソッドとしても利用できます:
jsTry
functionOuter () {this.y = 2;}Outer .Inner = function () {this.yy = 2;};Outer .innter ();
シンプルな名前空間を作成することにも使えます:
jsTry
varns = {};ns .C = class {};ns .func = function () {};ns ;
その他の変形も同様に可能です:
jsTry
// IIFE (即時実行関数式)varns = (function (n ) {returnn || {};})();ns .CONST = 1;// グローバル名前空間をデフォルトにするvarassign =assign ||function () {// ここにコードを記述する};assign .extra = 1;
オブジェクトリテラルは無制限型である
.ts
ファイルにおいて、変数宣言を初期化するオブジェクトリテラルは、宣言に型を与えます。
元のリテラルで指定されていない新しいメンバを追加することはできません。
このルールは.js
ファイルでは緩和されています; オブジェクトリテラルには無制限型(インデックスシグネチャ)があり、元々定義されていないプロパティを追加したり検索したりすることができます。
例えば:
jsTry
varobj = {a : 1 };obj .b = 2; // これは許容されます
オブジェクトリテラルはインデックスシグネチャ [x:string]: any
を持っているかのように動作し、制限をもつオブジェクトではなく無制限のマップとして扱うことができます。
他の特別なJSチェックの動作と同様に、変数にJSDoc型を指定することでこの動作を変更することができます。例えば:
jsTry
/** @type {{a: number}} */varobj = {a : 1 };Property 'b' does not exist on type '{ a: number; }'.2339Property 'b' does not exist on type '{ a: number; }'.obj .= 2; b
null、undefined、空の配列の初期化子はanyまたはany[]型を付ける
nullまたはundefinedで初期化されたパラメータやプロパティは、厳密なnullチェックが有効化されていたとしても、any型になります。 []で初期化されたパラメータやプロパティは、厳密なnullチェックが有効化されていたとしても、any[]型になります。 唯一の例外は上記で説明した初期化子を複数持つプロパティの場合です。
jsTry
functionFoo (i = null) {if (!i )i = 1;varj =undefined ;j = 2;this.l = [];}varfoo = newFoo ();foo .l .push (foo .i );foo .l .push ("end");
関数のパラメータはデフォルトでは任意である
ES2015以前のJavascriptでは、パラメータが任意かどうかを指定する方法がないため、.js
ファイルの関数パラメータはすべて任意とみなされます。
宣言されたパラメータ数よりも少ない引数で、関数を呼び出すことが可能です。
宣言された数より、引数の数が多い関数を呼び出すとエラーになるので注意が必要です。
例えば:
jsTry
functionbar (a ,b ) {console .log (a + " " +b );}bar (1); // OK、二番目の引数は任意とみなされますbar (1, 2);Expected 0-2 arguments, but got 3.2554Expected 0-2 arguments, but got 3.bar (1, 2,3 ); // エラー、引数が多すぎます
JSDocの注釈付き関数はこの規則から除外されます。
任意であることを表すには、JSDocの任意パラメータ構文([
]
)を使用します。例えば:
jsTry
/*** @param {string} [somebody] - 誰かの名前*/functionsayHello (somebody ) {if (!somebody ) {somebody = "John Doe";}console .log ("Hello " +somebody );}sayHello ();
可変長引数のパラメータ宣言は、arguments
の使い方から推測される
関数本体でarguments
を参照すると、暗黙的に可変長引数パラメータ(つまり: (...arg: any[]) => any
)を持っているとみなされます。JSDocの可変長引数構文を使って、引数の型を指定します。
jsTry
/** @param {...number} args */functionsum (/* numbers */) {vartotal = 0;for (vari = 0;i <arguments .length ;i ++) {total +=arguments [i ];}returntotal ;}
未指定の型パラメータのデフォルトはany
である
Javascriptにはジェネリクス型のパラメータを指定するための標準構文がないため、未指定の型パラメータはデフォルトでany
となります。
extends句において
例えば、React.Component
は、Props
とState
の2つの型パラメータを持つように定義されています。
.js
ファイルでは、extends句でこれらを指定する正しい方法はありません。型引数はデフォルトでany
となります:
js
import { Component } from "react";class MyComponent extends Component {render() {this.props.b; // this.propsはany型なので、許可されています}}
JSDocの@augments
を使って明示的に型を指定します。例えば:
js
import { Component } from "react";/*** @augments {Component<{a: number}, State>}*/class MyComponent extends Component {render() {this.props.b; // エラー: b は {a:number} に存在しません}}
JSDocリファレンスにおいて
JSDocで指定されていない型引数のデフォルトはanyです:
jsTry
/** @type{Array} */varx = [];x .push (1); // OKx .push ("string"); // OK、xはArray<any>型です/** @type{Array.<number>} */vary = [];y .push (1); // OKy .push ("string"); // エラー、stringはnumberに代入できません
関数呼び出しにおいて
ジェネリクス関数の呼び出しでは、引数から型パラメータを推論します。この処理では、主に推論の根拠がないために型の推論に失敗することが時たまあります。そのような場合、型パラメータはデフォルトでany
となります。例えば:
js
var p = new Promise((resolve, reject) => {reject();});p; // Promise<any>;
JSDocで利用可能なすべての機能を知りたい場合は、リファレンスを参照してください。