TypeScriptつかってますか??
使ってますか?
ぼくはバキバキ使っております。
TypeScriptの所感とかはまた別の話にするとして、今回はTypeScriptのオプション「strict」についてのお話です。
TypeScriptのstrictオプションとは?
既にがっつりTypeScriptを書いている方ならとっくにご存じでしょうが、TypeScriptにはコンパイラのチェックを厳しくするモードが存在します。それがstrictです。
tsconfigでstrict:trueを記述してあげると適用されます。
"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になります。
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はbindmcallもapplyも使っていなかったのでヨシ!
- --strictFunctionTypesも特に反した代入をしている箇所がなかったためヨシ!
とりあえずここら辺は現状のコードでは一切エラーが出なかったためtrueにしました。
【第二段階】エラーが少ないものを適用
次にエラーが少なかった「--noImplicitAny」を有効にしました
(エラー対応については頑張れたら別で記事にするかも)
基本的には、変数、引数などの型定義がされていないものや、型定義ファイルが入っていないパッケージへの対応になると思います。
【第三段階】 残ったしんどそうなやつ
--strictNullChecksと--strictPropertyInitializationが残ったので、こちらの対応をしていこうと思います。
結構これがエラー数が多くて大変でした...
StrictNullCheck
主にやったことは、
- 関数の戻り値の型定義をundefined,nullまで指定
- interfaceや、クラスの型定義を見直し
- nullやundefinedをはじく処理を追加
って感じです。
このオプションはnullとundefinedを暗黙的に許容することはないので、まず、定義していた値がnullを取りうるのかどうかを再度精査し、nullをとりうるものには型定義に nullを含めてあげました。
class Person {
name: string | null; // こんな感じ
}
こちらが定義した型に基づいたうえで厳密なチェックを行うため、データベースなどの定義や、データの型などが指定されているシステムはこの作業は一番最初にやってた方がいいと思います。
たぶん後から苦しむことになるかも。。。
strictPropertyInitialization
変数がnull、undefinedを取らないことを明示的に示さなければなりません。
TypeScriptにはNon-null assertion operatorといううものがあります。名前のとおりnullやundeinedを受け付けないことを示すことができます。
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