TypeScript Handbook を読み進めていく第二十二回目。
- Basic Types
- Variable Declarations
- Interfaces
- Classes
- Functions
- Generics
- Enums
- Type Inference
- Type Compatibility
- Advanced Types
- Symbols
- Iterators and Generators
- Modules
- Namwspaces
- Namespaces and Modules
- Module Resolution
- Declaration Merging
- JSX
- Decorators
- Mixins
- Triple-Slash Directives
- Type Checking JavaScript Files (今ココ)
Type Checking JavaScript Files
TypeScript 2.3 から --checkJs
を指定することで .js
ファイルの型チェックを行えるようになりました。
// @ts-nocheck
コメントを追加することで、特定のファイルだけチェックをスキップすることができます。
逆に、--checkJs
を指定せずに // @ts-check
コメントを追加することで、特定の .js
ファイルのみ、チェックを行うことも可能です。
また、特定の行だけエラーを無視したい場合、その前の行に // @ts-ignore
を追加してください。
Using types in JSDoc
.js
ファイルにおいても、.ts
ファイルと同様に型の推論が行われます。
型を推論できない場合、JSDoc を使用して .ts
ファイルの型アノテーションと同じ方法で型を指定します。
変数宣言に付けられた JSDoc アノテーションは宣言の型を指定するために使用されます。
/** @type {number} */
var x;
x = 0; // OK
x = false; // エラー: boolean は number に代入できない
サポートされている JSDoc の一覧は JSDoc サポート を参照してください。
Property declaration inferred from assignments in class bodies
ES2015/ES6 にはクラスのプロパティを宣言する方法がなく、プロパティは動的に代入されます。
.js
ファイルでは、クラスボディ内でのプロパティへの代入を基に、プロパティ宣言を推測します。
プロパティの型はすべての代入文の右辺に指定された値の型の共用型になります。
コンストラクタ内で定義されたプロパティは常に存在するとみなされ、メソッド、ゲッター、セッター内で定義されたプロパティは任意のプロパティとみなされます。
JSDoc で代入文を修飾することでプロパティの型を指定することも可能です。
class C {
constructor() {
/** @type {number | undefined} */
this.prop = undefined;
}
}
let c = new C();
c.prop = 0; // OK
c.prop = "string"; // エラー: 文字列は number|undefined に代入できない
クラスボディ内で一度も代入されないプロパティは不明とみなされます。
もしも読み取りしか行わないプロパティがある場合、コンストラクタで undefined に初期化することを検討してください。(例: this.prop = undefined;
)
「不明」扱いになった時には何が起きるんだろう?
CommonJS module input support
.js
ファイルでは CommonJS モジュール形式に対応しています。
module.exports
がエクスポート宣言として認識される他、require
関数がモジュールのインポート宣言として認識されます。
// "fs" モジュールのインポート
const fs = require("fs");
// readFile 関数のエクスポート
module.exports.readFile = function(f) {
return fs.readFileSync(f);
}
Object literals are open-ended
デフォルトでは変数宣言時のオブジェクトリテラルが変数の型となり、初期化時に指定していなかったメンバを新たに追加することはできません。
.js
ファイルではこのルールが緩められており、オブジェクトリテラルは変更可能な型として扱われます。
つまり、元々定義されていないメンバを追加・参照することが可能です。
var obj = { a: 1 };
obj.b = 2; // 許可されている
このように変更可能なオブジェクトとして扱うため、オブジェクトリテラルはデフォルトでインデックスシグネチャ [x:string]: any
を持つようになります。
他の JS チェックの振る舞いと同じように、変数に JSDoc type
を付与することで振るまいを変更することが可能です。
/** @type */
var obj = { a: 1 };
obj.b = 2; // エラー, {a: number} 型はプロパティ b を持たない
Function parameters are optional by default
JavaScript では (デフォルト引数を指定することなしに) 引数を任意にする方法がないため、.js
ファイルでは関数の引数はすべて任意とみなされます。
つまり、少ない引数での呼び出しが許可されているということです。
一方で、引数が多すぎる場合はエラーになるということを覚えておいてください。
function bar(a, b){
console.log(a + " " + b);
}
bar(1); // OK、第二引数は任意とみなされている
bar(1, 2);
bar(1, 2, 3); // エラー、引数が多すぎる
関数に JSDoc アノテーションを付与することでこの振る舞いを変更可能です。
JSDoc の optional parameter 構文を使用して任意の引数を表現してください。
/**
* @param {string} [somebody] - Somebody's name.
*/
function sayHello(somebody) {
if (!somebody) {
somebody = 'John Doe';
}
alert('Hello ' + somebody);
}
sayHello();
任意の引数は引数名を
[]
で囲むようだ
Var-args parameter declaration inferred from use of arguments
関数内で arguments
を参照している場合、その関数は可変数引数を受け付ける (つまり (...arg: any[]) => any
) とみなされます。
引数の型を指定する場合は JSDoc の var-arg 構文を使用してください。
Unspecified type parameters default to any
ジェネリクスの型パラメータが指定されていない場合、デフォルトで any
となります。
In extends clause
例えば、React.Component
は Props
と State
というふたつの型パラメータを定義していますが、.js
ファイルでは extends 句の中でこれらの型パラメータを指定する方法はありません。
そのため、デフォルトで any
となります。
import { Component } from "react";
class MyComponent extends Component {
render() {
this.props.b; // this.props は any 型となるため、許可されている
}
}
型を明示する場合は JSDoc @augments
を使用してください。
import { Component } from "react";
/**
* @augments {Component<{a: number}, State>}
*/
class MyComponent extends Component {
render() {
this.props.b; // エラー: b は {a:number} に存在しない
}
}
In JSDoc references
JSDoc 内でジェネリクスの型パラメータが指定されていない場合、デフォルトで any
となります。
/** @type{Array} */
var x = [];
x.push(1); // OK
x.push("string"); // OK、x は Array<any> 型
/** @type{Array.<number>} */
var y = [];
y.push(1); // OK
y.push("string"); // エラー、文字列は number に代入できない
In function calls
ジェネリック関数の呼び出し時には、引数を基に型パラメータを推論します。
しかし、型を推論するための情報が不足しているなどの理由から、推論に失敗することがあります。
この場合、型パラメータはデフォルトで any
となります。
var p = new Promise((resolve, reject) => { reject() });
p; // Promise<any>;
reject()
が何を返すか分からないからany
にせざるを得ない、というわけだ