LoginSignup
1
1

More than 5 years have passed since last update.

TypeScript Handbook を読む (22. Type Checking JavaScript Files)

Posted at

TypeScript Handbook を読み進めていく第二十二回目。

  1. Basic Types
  2. Variable Declarations
  3. Interfaces
  4. Classes
  5. Functions
  6. Generics
  7. Enums
  8. Type Inference
  9. Type Compatibility
  10. Advanced Types
  11. Symbols
  12. Iterators and Generators
  13. Modules
  14. Namwspaces
  15. Namespaces and Modules
  16. Module Resolution
  17. Declaration Merging
  18. JSX
  19. Decorators
  20. Mixins
  21. Triple-Slash Directives
  22. 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 アノテーションは宣言の型を指定するために使用されます。

JavaScript
/** @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 で代入文を修飾することでプロパティの型を指定することも可能です。

JavaScript
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 関数がモジュールのインポート宣言として認識されます。

JavaScript
// "fs" モジュールのインポート
const fs = require("fs");


// readFile 関数のエクスポート
module.exports.readFile = function(f) {
    return fs.readFileSync(f);
}

Object literals are open-ended

デフォルトでは変数宣言時のオブジェクトリテラルが変数の型となり、初期化時に指定していなかったメンバを新たに追加することはできません。
.js ファイルではこのルールが緩められており、オブジェクトリテラルは変更可能な型として扱われます。
つまり、元々定義されていないメンバを追加・参照することが可能です。

JavaScript
var obj = { a: 1 };
obj.b = 2;  // 許可されている

このように変更可能なオブジェクトとして扱うため、オブジェクトリテラルはデフォルトでインデックスシグネチャ [x:string]: any を持つようになります。

他の JS チェックの振る舞いと同じように、変数に JSDoc type を付与することで振るまいを変更することが可能です。

JavaScript
/** @type  */
var obj = { a: 1 };
obj.b = 2;  // エラー, {a: number} 型はプロパティ b を持たない

Function parameters are optional by default

JavaScript では (デフォルト引数を指定することなしに) 引数を任意にする方法がないため、.js ファイルでは関数の引数はすべて任意とみなされます。
つまり、少ない引数での呼び出しが許可されているということです。

一方で、引数が多すぎる場合はエラーになるということを覚えておいてください。

JavaScript
function bar(a, b){
    console.log(a + " " + b);
}

bar(1);       // OK、第二引数は任意とみなされている
bar(1, 2);
bar(1, 2, 3); // エラー、引数が多すぎる

関数に JSDoc アノテーションを付与することでこの振る舞いを変更可能です。
JSDoc の optional parameter 構文を使用して任意の引数を表現してください。

JavaScript
/**
 * @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.ComponentPropsState というふたつの型パラメータを定義していますが、.js ファイルでは extends 句の中でこれらの型パラメータを指定する方法はありません。
そのため、デフォルトで any となります。

JavaScript
import { Component } from "react";

class MyComponent extends Component {
    render() {
       this.props.b; // this.props は any 型となるため、許可されている
    }
}

型を明示する場合は JSDoc @augments を使用してください。

JavaScript
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 となります。

JavaScript
/** @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 となります。

JavaScript
var p = new Promise((resolve, reject) => { reject() });

p; // Promise<any>;

reject() が何を返すか分からないから any にせざるを得ない、というわけだ

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1