tsconfig.jsonとstrictモードの移行について

tsconfig.json は TypeScript の設定ファイルです。
TypeScript は型定義によって安全性の高いコードを書くことが出来ますが、tsconfig の設定次第では最大の恩恵を受けることが出来ません。

ここでは主要なオプションの解説と、プロジェクト過程で strictモードを移行する手順について記載していきます。

## オプション
tsconfig は json ファイルなので、以下のような形式で記載します。
```json
{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "lib": ["dom", "es2015"],
    "outDir": "./dist",
    "sourceMap": true,
    "declaration": true,
    "declarationMap": true,
    "inlineSourceMap": false,
    "inlineSources": true,
    "strict": true,
    "moduleResolution": "node",
    "baseUrl": "./",
    "paths": {
      "*": ["types/*"]
    },
    "rootDirs": ["src/views", "src/components"],
    "typeRoots": ["./node_modules/@types"],
    "types": ["node", "jest"],
    "allowJs": true,
    "checkJs": false,
    "jsx": "react",
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": [
    "src/**/*"
  ],
  "exclude": [
    "node_modules",
    "**/*.spec.ts"
  ]
}
```

### compilerOptions
TypeScript コンパイラに関するオプションを設定します。
オプションは数多くあるので、主要なものをピックアップしています。

|オプション名  |内容  |
|-----|-----|
|target  |コンパイルされる JavaScript のバージョンを指定する。  |
|module  |使用するモジュールシステムを指定する。バックエンドは`commonjs`。フロントエンドは`es2015,esnext`など。  |
|lib  |コンパイラに含めるライブラリファイル。target に使用したい機能がない場合、追加で指定する。  |
|outDir  |コンパイルされた JavaScript ファイルを出力するディレクトリを指定する。 |
|sourceMap  |ソースマップを生成するかどうか。  |
|declaration  |`.d.ts` 型宣言ファイルを生成するかどうか。  |
|declarationMap  |`.d.ts`ファイルに対してソースマップを生成するかどうか。  |
|inlineSourceMap  |ソースマップを単一のファイルとして出力ファイルにインラインで含めるかどうか。  |
|inlineSources  |ソースマップにソースコードの内容をインラインで含めるかどうか。  |
|strict  |厳格な型チェックを行うかどうか。  |
|moduleResolution  |モジュールのインポート文で指定されたモジュールの解決方法をコンパイラに設定する（`node`0や`classic`など） |
|baseUrl  |モジュール名を解決する際のベースディレクトリを指定する。  |
|paths  |`baseUrl`を基準にしたインポートパスのエイリアスを指定する。  |
|rootDirs  |ルートディレクトリのリストを指定し、実行時にそれらをマージしたものとして扱う。  |
|typeRoots  |型定義ファイルがあるディレクトリのリスト。  |
|types  |コンパイルに含める型定義ファイル。  |
|allowJs  |JavaScriptファイルのコンパイルを許可するかどうか。  |
|checkJs  |JavaScriptファイルでのエラーチェックを有効にするかどうか。  |
|jsx  |コンパイラがJSX構文を解釈する形式を設定する（`preserve`, `react-native`, `react`など） |
|skipLibCheck  |コンパイル中に`.d.ts`などの宣言ファイルの型チェックをスキップするかどうか。  |
|forceConsistentCasingInFileNames  |ファイルインポート時の大文字小文字の区別をするかどうか。  |

### include
TypeScript コンパイラがコンパイルするファイルのパターンを指定します。
include の指定が無い場合、コンパイラはデフォルトで全ての`.ts、.tsx、.d.ts`ファイルをコンパイル対象とします。

### exclude
コンパイルから除外するファイルのパターンを指定します。
exclude の指定が無い場合、コンパイラはデフォルトで`node_modules`と`bower_components`ディレクトリ、そして`outDir`に指定されたディレクトリを除外します。

## strict
strictモードを有効にすると、以下のようなオプションがすべて true になり、厳格な型チェックが行われるようになります。

### noImplicitAny
暗黙的な any 型を許可しません。
以下の場合、引数 a, b の型が指定されず、暗黙的に any 型となりエラーが発生します。
```TypeScript
function add(a, b) {
  return a + b;
}
```

### noImplicitThis
this の暗黙的な any 型を許可しません。
以下は innerFunction() をアロー関数ではなく通常関数として宣言しているため、this はグローバルオブジェクトまたは undefined となり、this の型が暗黙的に any となります。

```TypeScript
const myObject = {
  property: "Hello, world!",
  showProperty() {
    function innerFunction() {
      console.log(this.property); 
    }
    innerFunction();
  }
};

myObject.showProperty();
```

エラーを解消するには、アロー関数または bind を使用して this を束縛する必要があります。

### strictNullChecks
null と undefined を厳格にチェックし、代入を許可しません。
strictNullChecks が false の場合、null と undefined をチェックしないので、以下のコードは TypeScript ではエラーになりません。

```TypeScript
const date: Date = null;
const error: Error = undefined;
```
ただし、これらのプロパティを参照しようとすると JavaScript エラーが発生します。
strictNullChecks が true の場合、非 null 型への null の代入、非 undefined 型への undefined の代入はコンパイルエラーになります。

### strictFunctionTypes
関数のパラメータと戻り値の型を厳格にチェックし、引数の共変性を許可しない。
以下のように、引数の型を広められる特性を引数の反変性と言います。

```TypeScript
let func: (n: number | null) => any;
func = (n: number | null | undefined) => {}; // OK
```

以下のように、引数の型を狭められる特性を引数の共変性と言います。

```TypeScript
let func: (n: number | null) => any;
func = (n: number) => {}; // OK
```

この両特性を引数の双変性と言います。
引数の双変性を許容する場合、以下は実行時エラーとなります。

```TypeScript
let func: (n: number | null) => any;
// 共変性によって型を狭めている
func = (n: number) => n.toString();
// funcはnull型を許容するのでnullを渡すことが出来るが、共変性による矛盾が生じて実行時エラーとなる
func(null);
```

strictFunctionTypes を true とした場合、共変性が許可されないので、型を狭めた段階でコンパイルエラーとなります。

### strictPropertyInitialization
クラスのプロパティがコンストラクタで初期化されていることを保証します。
strictPropertyInitialization の true の場合、以下はクラスプロパティの初期化が行われていないためコンパイルエラーとなります。

```TypeScript
class User {
  id: number;        // 初期化されていないためエラーになる
  name: string;      // 初期化されていないためエラーになる
  optionalProperty?: string; // オプショナルプロパティは初期化が不要
}
```

以下のように、宣言時またはコンストラクタで初期化を行うことを強制します。

```TypeScript
class User {
  id: number = 0;          // 宣言時の初期化
  name: string;
  optionalProperty?: string;

  constructor() {
    this.name = "Bob";
  }
}
```

### strictBindCallApply
bind、call、apply の型チェックを行い、戻り値の型は呼び出す関数の戻り値型とする。
strictBindCallApply が false の場合、bind、call、apply の型チェックを行いません。
そのため、以下は警告が出ません。
```TypeScript
function addNumbers(x: number, y: number): number {
  return x + y;
}

// 数値型に文字列を渡しているが、警告は発生しない
const sum = addNumbers.call(null, 10, "30"); 
```

strictBindCallApply を true とすることでコンパイルエラーを発生させることが出来るようになります。

### useUnknownInCatchVariables
catch ブロック内の例外変数の型を any から unknown 型として解釈する。
useUnknownInCatchVariables を利用しない場合、以下の `err` は any 型になります。

```TypeScript
try {
  throw new Error();
} catch (err) {
  // ...
}
```
useUnknownInCatchVariables はエラー変数の型 unknown 型に変更します。
unknown 型は any 型よりも型安全であり、any 型は、コンパイラが型チェックをスキップするため、どのような操作も許可されてしまいますが、unknown 型は、型に関する具体的なアサーションやチェックを行うまで、変数を操作することはできません。

## strictモードと移行
TypeScript は、strictモードを有効化することで最大の恩恵を受けることが出来るようになります。
しかし、JavaScript からの移行で多数の型エラーが発生するような環境では、暫定的に strict モードを無効にして導入を行うこともあります。

上記のようなケースで、strict モードを無効にした環境で開発がある程度進み、その後に strict モードを切り替えたい場合、数多くの型エラーに対処する必要があります。
膨大なエラーを解消するまとまった工数を取るのは困難であるため、順を追ってプロパティを許可していく必要があります。

コンパイラオプションの strict に含まれるプロパティは、それぞれ個別に指定できます。

```JSON
{
  "compilerOptions": {
    "strict": false,
    "strictNullChecks": true
  }
}
```
このように設定すると、strict に含まれるプロパティの中で、strictNullChecks だけが有効の状態になります。
一度に strict を true にしてしまうと、strict に含まれる全てのオプションに関するエラーと向き合う必要があるので、オプションを個別に分解して作業を進めていきます。

### チェック対象のディレクトリを分解する
JavaScript からの移行の場合、noImplicitAny などの型エラーは広範囲で発生します。
このような場合では、`tsconfig.json` をディレクトリ別に用意して、作業を進めていきます。

`tsconfig.dir.json`などのファイルを新たに作成します。

```JSON
{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "strictNullChecks": true,
  },
  "include": [
    "src/**/*"
  ]
}
```
`extends`で既存の tsconfig を継承し、必要なオプションをオーバーライドします。
`include`でコンパイル対象のディレクトリを指定します。このディレクトリを絞り込むことで、プロパティ変更による影響を限定し、作業を進めやすくします。

### コンパイルチェックをかける
package.json を以下のように追記します。
```JSON
 "scripts": {
    "tsc-dir": "tsc --project tsconfig.dir.json"
  }
```
tsc は TypeScript ファイルのコンパイルチェックを行うコマンドです。
オプションを指定しない場合、プロジェクトのルートにある tsconfig.json に従います。

`--project` オプションは tsc コマンドに対して、設定ファイルを指定する際に使用します。
ここで、先ほど作成した tsconfig.dir.json を指定します。

頻繁に実行することが想定されるので、package.json でスクリプトエイリアスを設定していますが、直接コマンドを叩いても問題ありません。

このコマンドを実行することによって、tsconfig.dir.json に記載した影響範囲でのみコンパイルチェックが行われます。
このように作業範囲を狭めて対応を行うと、strict の移行をスムーズに実施することができます。