この記事はフラーアドベントカレンダー 2019 23日目の記事です。
noImplicitAnyとは?
TypeScriptには多くのCompiler Optionsが存在します。Compiler Options
の設定によって、型に対しての書き方を厳密にすることも、緩くすることもできます。
型の厳密さに関するTypeScriptのCompiler Options
の代表的なもの一つは--noImplicitAny
です。--noImplicitAny
の公式ドキュメントの説明は次の通りです。
Raise error on expressions and declarations with an implied any type.
実際のコードでみてみましょう。次のようなコードがあります。
function greeting(name) {
return `Hello ${name}!`;
}
console.log(greeting("Ryota"));
このコードは、Compiler Options
として--noImplicitAny
を有効にしていた場合、次のようなエラーとなります。
TSError: ⨯ Unable to compile TypeScript:
main.ts:1:22 - error TS7006: Parameter 'name' implicitly has an 'any' type.
1 function loadMessage(name) {
1行目のgreeting関数のnameパラメーターが原因でコンパイルエラーになっています。
--noImplicitAny
は、「型を何も書かない場合は暗黙的にanyになるけど、それはだめで、何かしら型を明示的にしないとだめだよ。」というCompiler Options
です。このコンパイルエラーを解決するために、次のように型を明示してあげましょう。
function greeting(name: string) {
return `Hello ${name}!`;
}
console.log(greeting("Ryota"));
greeting関数は、stringをパラメーターとしてとることを明示的にしました。
ちなみに次のコードでも大丈夫です。
function greeting(name: any) {
return `Hello ${name}!`;
}
console.log(greeting("Ryota"));
暗黙的なany型はだめですが、明示的にany型を記述することは全く問題ありません。
suppressImplicitAnyIndexErrorsとは?
Compiler Options
として--noImplicitAny
が有効になっている場合、次のコードはコンパイルエラーになります。
const gradeFactors = {
gradeA: 1.0,
gradeB: 0.5,
gradeC: 0.3
};
const grade: string = 'gradeA';
const factor = gradeFactors[grade];
console.log(`${grade} : ${factor}`);
エラーは次の通りです。
main.ts:9:16 - error TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ gradeA: number; gradeB: number; gradeC: number; }'.
No index signature with a parameter of type 'string' was found on type '{ gradeA: number; gradeB: number; gradeC: number; }'.
9 const factor = gradeFactors[grade];
string
は、{ gradeA: number; gradeB: number; gradeC: number; }
のインデックスタイプとして使えないからです。インデックスアクセスの部分がコンパイルエラーとなります。
これを解決する方法は2つあります。
1つ目はアクセスできるように型を変える方法です。
gradeの型を次のようにstring
から「gradeA
| gradeB
| gradeC
」などにすることでコンパイルエラーを解消することができます。
const gradeFactors = {
gradeA: 1.0,
gradeB: 0.5,
gradeC: 0.3
};
const grade: 'gradeA' | 'gradeB' | 'gradeC' = 'gradeA';
// これ以外にも以下などでもOK
// const grade = 'gradeA';
// const grade: 'gradeA' = 'gradeA';
// const grade: 'gradeA' | 'gradeB' = 'gradeA';
// または次のようにGrade型を作るのも有効
// type Grade = 'gradeA' | 'gradeB' | 'gradeC';
// const grade: Grade = 'gradeA';
const factor = gradeFactors[grade];
console.log(`${grade} : ${factor}`);
2つ目はコンパイルオプションを追加することです。
Compiler Options
に--suppressImplicitAnyIndexErrors
というオプションがあります。
これは「厳密な型チェックを一部妥協する」コンパイルオプションです。
--noImplicitAny
が有効な時に、先のコンパイルエラーのようなインデックスアクセスによるコンパイルエラーを抑制する機能があります。
先のコードは--noImplicitAny
に加えて、--suppressImplicitAnyIndexErrors
を有効にすることで、コンパイルエラーを解消することができます。
実プロダクトでnoImplicitAnyを導入するために妥協する
実プロダクトで--noImplictAny
を導入することを考えてみましょう。例えば、コードベースはある程度大きく、すでに数年開発が続き、複数人で開発していて、お金も生み出している実プロダクトで、です。そんなプロダクトを改善するために、今まで導入されていなかった--noImplictAny
を導入しましょう。
--noImplictAny
を導入するために、先に説明した「フィールドやメソッドのパラメータの型が明示的になっていない箇所」、「無効な型でインデクサにアクセスしている箇所」を修正する必要があります。
フィールドやメソッドのパラメータに対して、単純に型を明示する作業はある程度簡単です。(実際は変更箇所が多かったり、同僚とコンフリクトしないように気をつける必要があり、そんなに簡単ではありません。)
一方で、「無効な型でインデクサにアクセスしている箇所」を修正する作業はかなり大変になる場合があります。型を定義・変更しないといけない、その型の変更が伝版することがあるからです。
筆者個人の意見として、--noImplictAny
を導入するために、--suppressImplicitAnyIndexErrors
を使って妥協することは、有効な戦略だと思います。