メインコンテンツへ移動

JavaScript ファイルのチェック_TypeScript ノート 18

無料2019-05-05#TypeScript#TypeScript check js file#checkJS not working#JavaScript类型检查#JavaScript严格类型

TypeScript のタイプチェックは .ts に限定されず、.js もサポート

はじめに

TypeScript のタイプチェックは .ts に限定されず、.js もサポートします

しかしファイル内容が標準的な JavaScript コードのみを含むことを保証するため.js ファイルは ES 構文規範に従ってチェックされるため、TypeScript タイプ注釈の出現は許可されません:

.js files are still checked to ensure that they only include standard ECMAScript features; type annotations are only allowed in .ts files and are flagged as errors in .js files.

したがって JSDoc を通じて JavaScript に追加のタイプ情報を追加します:

JSDoc comments can be used to add some type information to your JavaScript code, see JSDoc Support documentation for more details about the supported JSDoc constructs.

同時に、.js に対するタイプチェックは比較的緩やかで、.ts のタイプチェックとは異なります。差異は主に 3 方面に集中しています:

  • タイプ注釈方式

  • デフォルトレタイプ

  • タイプ推論戦略

P.S. 緩やかな戦略のため、noImplicitAnystrictNullChecks などの厳密チェックマークは .js 内でもそれほど信頼できません

一.チェックを开启

--allowJs オプションは JavaScript ファイルのコンパイルを許可しますが、デフォルトではこれらのファイルに対してタイプチェックを行いません。--checkJs オプションをさらに开启しない限り、すべての .js ファイルに対して検証を行います

OptionTypeDefaultDescription
--allowJsbooleanfalseAllow JavaScript files to be compiled.
--checkJsbooleanfalseReport errors in .js files. Use in conjunction with --allowJs.

另外、TypeScript はタイプチェックを制御するために使用されるいくつかの特殊な注釈をサポートしています:

  • // @ts-nocheck:ファイルレベル、タイプチェックをスキップ

  • // @ts-check:ファイルレベル、タイプチェックを実行

  • // @ts-ignore:行レベル、タイプエラーを無視

これらの注釈はより細粒度のタイプチェック制御を提供します。例えば一部の .js ファイルのみをチェックしたい場合、--checkJs オプションを开启せず、一部の .js ファイルの首行にのみ // @ts-check 注釈を追加します

二.タイプ注釈方式

.js ファイル内では JSDoc を通じてタイプを注釈します。例えば:

/**
 * @type {number}
 */
var x;

x = 0;
// エラー Type 'false' is not assignable to type 'number'.
x = false;

注意、JSDoc は注釈フォーマットに要件があります/** で始まるもののみを認識します:

JSDoc comments should generally be placed immediately before the code being documented. Each comment must start with a /** sequence in order to be recognized by the JSDoc parser. Comments beginning with /*, /***, or more than 3 stars will be ignored.

Adding documentation comments to your code から引用)

另外、すべての JSDoc マークがサポートされているわけではありません。ホワイトリストは Supported JSDoc を参照

三.デフォルトレタイプ

另一方面、JavaScript 内には大量の慣用「パターン」が存在するため、デフォルトレタイプ方面では相当緩やかです。主に 3 点に現れます:

  • 関数パラメータはデフォルトでオプション

  • 指定されていないタイプパラメータはデフォルトで any

  • タイプが緩やかなオブジェクトリテラル

関数パラメータはデフォルトでオプション

.js ファイル内のすべての関数パラメータはデフォルトでオプションです。したがって実参数数量が形参数数量より少ないことを許可しますが、余分なパラメータが存在する場合は依然としてエラーになります。例えば:

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

bar(1);
bar(1, 2);
// エラー Expected 0-2 arguments, but got 3.
bar(1, 2, 3);

注意、JSDoc でパラメータ必填を注釈した場合は例外です:

/**
 * @param {string} greeting - Greeting words.
 * @param {string} [somebody] - Somebody's name.
 */
function sayHello(greeting, somebody) {
  if (!somebody) {
      somebody = 'John Doe';
  }
  console.log('Hello ' + somebody);
}

// エラー Expected 1-2 arguments, but got 0.
sayHello();
sayHello('Hello');
sayHello('Hello', 'there');
// エラー Expected 1-2 arguments, but got 3.
sayHello('Hello', 'there', 'wooo');

JSDoc 注釈によると、上例中 greeting は必填、somebody はオプションです。したがって無参と 3 参はエラーになります

特殊的に、ES6 は [デフォルトパラメータと不定パラメータ](/articles/デフォルトパラメータと不定パラメータ-es6 ノート 4/) を通じて [オプションパラメータ](/articles/関数-typescript ノート 5/#articleHeader4) を暗黙的にマークできます。例えば:

/**
 * @param {string} somebody - Somebody's name.
 */
function sayHello(somebody = 'John Doe') {
  console.log('Hello ' + somebody);
}

// 正确
sayHello();

JSDoc 注釈(@param {string} somebody)から見ると somebody は必填ですが、デフォルトパラメータ(somebody = 'John Doe')は somebody がオプションであることを示します。タイプシステムはこれらの情報を総合して推論します

指定されていないタイプパラメータはデフォルトで any

JavaScript にはジェネリックパラメータを表すための構文が提供されていないため、指定されていないタイプパラメータはすべてデフォルトで any タイプになります

ジェネリックは JavaScript 中で主に 2 種類の形式で出現します:

  • ジェネリッククラスを継承し、Promise 等を作成(ジェネリッククラス、Promise 等は外部 d.ts に定義)

  • 其它カスタムジェネリック(JSDoc でジェネリックタイプを明示)

例えば:

// ジェネリッククラスを継承 - .js
import { Component } from 'react';
class MyComponent extends Component {
  render() {
    // 正确 this.props.unknownProp は any タイプ
    return <div>{this.props.unknownProp}</div>
  }
}

その中で this.props はジェネリックタイプを持ちます:

React.Component<any, any, any>.props: Readonly<any> & Readonly<{
  children?: React.ReactNode;
}>

.js 内でジェネリックパラメータのタイプを指定しない場合、デフォルトで any になるため、エラーになりません。しかし同じコードを .tsx 内ではエラーになります:

// .tsx
import { Component } from 'react';
class MyComponent extends Component {
  render() {
    // エラー Property 'unknownProp' does not exist on type 'Readonly<{}> & Readonly<{ children?: ReactNode; }>'.
    return <div>{this.props.unknownProp}</div>
  }
}

Promise のシーンも同様です:

// .js
var p = new Promise((resolve, reject) => { reject(false) });
// p タイプは Promise<any>
p;

// .ts
const p = new Promise<boolean>((resolve, reject) => { reject(false) });
// p タイプは Promise<boolean>
p;

この外部宣言(d.ts)からのジェネリックの他に、もう一種類のカスタム*「JavaScript ジェネリック」*があります:

// .js ジェネリックを宣言するが、タイプパラメータを記入しない
/** @type{Array} */
var x = [];
x.push(1);        // OK
x.push("string"); // OK, x is of type Array<any>

// .js ジェネリックを宣言し、同時にタイプパラメータを指定
/** @type{Array.<number>} */
var y = [];

y.push(1);        // OK
y.push("string"); // Error, string is not assignable to number

つまり JSDoc で定義されたジェネリックで、タイプパラメータを指定しない場合、デフォルトで any になります

タイプが緩やかなオブジェクトリテラル

.ts 内では、オブジェクトリテラルで変数を初期化すると同時に該変数のタイプを確定し、オブジェクトリテラルに新メンバーを追加することを許可しません。例えば:

// .ts
// obj タイプは { a: number; }
let obj = { a: 1 };
// エラー Property 'b' does not exist on type '{ a: number; }'.
obj.b = 2;

.js 内では比較的緩やかです:

// .js
var obj = { a: 1 };
// 正确
obj.b = 2;

まるで インデックスシグネチャ [x:string]: any を持っているかのようです;

// .ts
let obj: { a: number; [x: string]: any } = { a: 1 };
obj.b = 2;

同樣に、JavaScript 内でも JSDoc を通じてその確切なタイプを明示できます:

// .js
/** @type {{a: number}} */
var obj = { a: 1 };
// エラー Property 'b' does not exist on type '{ a: number; }'.
obj.b = 2;

四.タイプ推論戦略

タイプ推論は代入推論とコンテキスト推論に分かれます。.js に対してはいくつかの针对性的な推論戦略があります

代入推論:

  • Class メンバー代入推論

  • 構築関数はクラスと等価

  • nullundefined[] 代入推論

コンテキスト推論:

  • 不定パラメータ推論

  • モジュール推論

  • 名前空間推論

Class メンバー代入推論

.ts 内ではクラスメンバー宣言中の初期化代入を通じてインスタンス属性のタイプを推論します:

// .ts
class Counter {
  x = 0;
}
// x タイプは number と推論
new Counter().x++;

しかし ES6 Class は インスタンス属性を宣言する構文 を提供していません。クラス属性は動的代入を通じて作成されます。このような JavaScript 慣用「パターン」も推論できます。例えば:

class C {
  constructor() {
    this.constructorOnly = 0;
    this.constructorUnknown = undefined;
  }
  method() {
    // エラー Type 'false' is not assignable to type 'number'.
    this.constructorOnly = false;
    this.constructorUnknown = "plunkbat";
    this.methodOnly = 'ok';
  }
  method2() {
    this.methodOnly = true;
  }
}

class 宣言中のすべての属性代入は(クラスインスタンス)タイプ推論の根拠となります。したがって上例中 C クラスインスタンスのタイプは:

// TypeScript
type C = {
  constructorOnly: number;
  constructorUnknown: string;
  method: () => void;
  method2: () => void;
  methodOnly: string | boolean
}

具体的なルールは以下の通り:

  • 属性タイプは構築関数中の属性代入を通じて確定

  • 構築関数中で定義されていない、または構築関数中でタイプが undefined または null の属性(此时は any)、そのタイプはすべての代入中右側値タイプの聯合

  • 構築関数中で定義された属性はすべて必ず存在するとみなし、其它場所(メンバーメソッドなど)で出現するものはオプションとして扱う

  • クラス宣言中に出現しない属性はすべて未定義で、アクセスするとエラー

構築関数はクラスと等価

另外、ES6 之前、JavaScript 内では構築関数でクラスを代用しました。TypeScript タイプシステムもこの「パターン」を「理解」できます(構築関数は ES6 Class と等価)。メンバー代入推論も同様に適用されます:

function C() {
  this.constructorOnly = 0;
  this.constructorUnknown = undefined;
}
C.prototype.method = function() {
  // エラー Type 'false' is not assignable to type 'number'.
  this.constructorOnly = false;
  this.constructorUnknown = "plunkbat";
}

nullundefined[] 代入推論

.js 内では、初期値が nullundefined の変数、パラメータまたは属性はすべて any タイプとみなされ、初期値が [] のものは any[] タイプとみなされます。例えば:

// .js
function Foo(i = null) {
  // i タイプは any
  if (!i) i = 1;  // i タイプは依然として any
  var j = undefined;  // j タイプは any
  j = 2;  // j タイプは any | number つまり number
  this.j = j;
  this.l = [];  // this.l タイプは any[]
}
var foo = new Foo();
foo.l.push(foo.j);
foo.l.push("end");

同樣に、多次代入時、タイプは各値タイプの聯合になります

不定パラメータ推論

.js 内では arguments の使用状況に応じて [不定パラメータ](/articles/デフォルトパラメータと不定パラメータ-es6 ノート 4/#articleHeader2) が存在するかを推論します。例えば:

// .js
function sum() {
  var total = 0
  for (var i = 0; i < arguments.length; i++) {
    total += arguments[i]
  }
  return total
}
// sum タイプは (...args: any[]) => number
sum(1, 2, 3);

もちろん、JSDoc を通じて不定パラメータを宣言することもできます:

// .js
/** @param {...number} args */
function sum(/* numbers */) {
  var total = 0
  for (var i = 0; i < arguments.length; i++) {
    total += arguments[i]
  }
  return total
}
// sum タイプは (...args: number[]) => number
sum(1, 2, 3);

モジュール推論

.js 内では、CommonJS モジュールに対して、exportsmodule.exports の属性代入をモジュール导出(export)として識別し、require 関数呼び出しはモジュール導入(import)に対応します。例えば:

// .js
// 等価于 `import module "fs"`
const fs = require("fs");

// 等価于 `export function readFile`
module.exports.readFile = function(f) {
  return fs.readFileSync(f);
}

P.S. 実際、TypeScript の CommonJS モジュールへのサポートはこのタイプ推論を通じて完成されます

名前空間推論

.js 内では、クラス、関数、オブジェクトリテラルはすべて名前空間とみなされます。それらは名前空間と非常に類似しているためです(すべて値とタイプの二重の意味を持ち、ネストをサポートし、かつ三者は結合して使用できます)。例えば:

// .js
class C { }
C.D = class { }
// または
function Cls() {}
Cls.D = function() {}

new C.D();
new Cls.D();

特にオブジェクトリテラルは、ES6 之前は元々名前空間として使用されていました:

var c = {};
ns.D = class {}
ns.F = function() {}

new c.D();
new c.F();

参考資料

コメント

コメントはまだありません

コメントを書く