進行中のTypeScriptのプロジェクトに「strict: true」を適用させたときの話

# TypeScriptつかってますか？？

使ってますか？

ぼくはバキバキ使っております。
TypeScriptの所感とかはまた別の話にするとして、今回はTypeScriptのオプション「strict」についてのお話です。

# TypeScriptのstrictオプションとは？

既にがっつりTypeScriptを書いている方ならとっくにご存じでしょうが、TypeScriptにはコンパイラのチェックを厳しくするモードが存在します。それがstrictです。
tsconfigでstrict:trueを記述してあげると適用されます。

```typescript
  "compilerOptions": {
    "target": "es2017",                        
    "module": "commonjs",     
    "outDir": "./dist",                     
    "strict": true,                       
    "forceConsistentCasingInFileNames": true  
  },
```

# 実際にどうなるの？

strictをtrueにすると以下のオプションが有効になります。
--noImplicitAny, --noImplicitThis, --alwaysStrict, --strictBindCallApply, --strictNullChecks, --strictFunctionTypes , --strictPropertyInitialization

実際にどんな風になるのかは以下の通り

- --noImplicitAny  
型推論等で暗黙的にanyとなる箇所があるとエラー 
- --noImplicitThis    
関数内でthisの指定をしないでthisを使うとエラー  
- --alwaysStrict  
strictモードで分析され、各ソースファイルの先頭に「use strict」の指示を書き込む  
- --strictBindCallApply  
functionのbind,call,applyを使用したときのエラー内容がより詳細にわかるようになる  
- --strictNullChecks  
nullとundefinedは、それら自身の型かvoid型やany型以外に入らないようになる
- --strictFunctionTypes  
関数型のチェックが厳密になる。関数がcontravariantlyからbivariantlyになります。

```TypeScript
class Person {
  name: string;
  age: number;
}

class Otaku extends Person{
  inkyaLevel: number;
}

declare let acceptPerson: (x: Person) => void;
declare let acceptOtaku: (yoshimok: Otaku) => void;

acceptPerson = acceptOtaku; // --strictFunctionTypesがtrueだとError
acceptOtaku= acceptPerson;  // OK
```
- --strictPropertyInitialization   
undefinedを許容していないプロパティは、初期化していない場合には宣言できない (--strictNullChecksが有効である必要があります) 

# それじゃ移行しましょう

これまでTypeScriptは導入したもののstrictはfalseでした（そもそもあんまり理解していなかったので...）
というわけでstrict:trueにしたのはいいのですが、エラー数が**400**とかになってしまったので（思ってたよりは少なかった）、今回は段階的にオプションを追加していくことにしました。

**※これからの話はあくまで自分の環境での話です。使っているフレームワークや開発しているもので大きく変わってくると思うので参考程度に読んでもらえたらうれしいです。**

## 【第一段階】エラーがでなかったものに適用
ひとまずプロジェクトに影響がないものからtrueにしていくことに

幸い（？）プロジェクトの仕様もあってか、自分があまりいろんな書き方をせずにコーディングしていたのでエラーが全くでないものがあったため、それを先にtrueにしました

- --noImplicitThisはひっかかるものがなかったのでヨシ！
- --strictBindCallApplyはbindｍcallもapplyも使っていなかったのでヨシ！
- --strictFunctionTypesも特に反した代入をしている箇所がなかったためヨシ！

とりあえずここら辺は現状のコードでは一切エラーが出なかったためtrueにしました。

## 【第二段階】エラーが少ないものを適用

次にエラーが少なかった「--noImplicitAny」を有効にしました

（エラー対応については頑張れたら別で記事にするかも）

基本的には、変数、引数などの型定義がされていないものや、型定義ファイルが入っていないパッケージへの対応になると思います。

## 【第三段階】 残ったしんどそうなやつ
--strictNullChecksと--strictPropertyInitializationが残ったので、こちらの対応をしていこうと思います。

結構これがエラー数が多くて大変でした...

### StrictNullCheck
主にやったことは、

- 関数の戻り値の型定義をundefined,nullまで指定
- interfaceや、クラスの型定義を見直し
- nullやundefinedをはじく処理を追加

って感じです。

このオプションはnullとundefinedを暗黙的に許容することはないので、まず、定義していた値がnullを取りうるのかどうかを再度精査し、nullをとりうるものには型定義に nullを含めてあげました。

```typescript
class Person {
  name: string | null; // こんな感じ
}
```

こちらが定義した型に基づいたうえで厳密なチェックを行うため、データベースなどの定義や、データの型などが指定されているシステムはこの作業は一番最初にやってた方がいいと思います。  
たぶん後から苦しむことになるかも。。。


### strictPropertyInitialization

変数がnull、undefinedを取らないことを明示的に示さなければなりません。
TypeScriptにはNon-null assertion operatorといううものがあります。名前のとおりnullやundeinedを受け付けないことを示すことができます。

```typescript
let hoge!: number;

hoge = null;  // Error
```

これを用いることで、より厳密な型定義ができます。
エラーは大体これで解消できるはず

## 【最後】strict:trueに

--alwaysStrictはうまあじがよくわかんなかったので最後まで放置していました。  

これで晴れて「strict: true」になりました。これからは厳密なチェックのもとコーディングすることになります。

# まとめ

- strict: trueで手取り足取りチェックしてもらって、自分は処理を書くのに専念しましょう。
- 開発の途中で変更すると大変なことになるかもしれないので、なるべく早いうちに、ていうか最初から「strict: true」で開発しましょう
- 開発環境が動作しなくなったりするのを警戒しましょう。移行中にでたPRなどは十分に確認するか、一回適用させてみてエラーが出るか確認してもいいかもしれません（このための--alwaysStrictなのかな？）
- 適用させる中で、より厳密に型を定義してあげる必要があるので、自分の型定義に求める厳密さも自然と強くなる（と思う）

---  
これからはstrictはtrueでやってくぞ
次TypeScript使うときは真っ先にstrict:trueを設定してから書き始めると誓います。


## 参考にしたサイト

https://www.typescriptlang.org/docs/handbook/compiler-options.html
https://qiita.com/vvakame/items/79557e00cfe6d3c612cd