paint-brush
TypeScript の力を解き放つ: tsconfig における重要な考慮事項@nodge
2,174 測定値
2,174 測定値

TypeScript の力を解き放つ: tsconfig における重要な考慮事項

Maksim Zemskov13m2023/07/12
Read on Terminal Reader

長すぎる; 読むには

TypeScript は、強力な型システムと静的分析機能により、複雑なアプリケーションを構築するための人気のある言語です。ただし、最大限の型安全性を実現するには、tsconfig を正しく構成することが重要です。この記事では、最適な型安全性を実現するために tsconfig を構成するための重要な考慮事項について説明します。
featured image - TypeScript の力を解き放つ: tsconfig における重要な考慮事項
Maksim Zemskov HackerNoon profile picture
0-item
1-item

複雑な Web アプリケーションを構築している場合、プログラミング言語として TypeScript を選択することになるでしょう。 TypeScript は、強力な型システムと静的分析機能で広く愛されており、コードが堅牢でエラーがないことを保証するための強力なツールとなっています。


また、コード エディターとの統合により開発プロセスが高速化され、開発者がより効率的にコードを操作し、より正確なヒントやオートコンプリートを取得できるようになり、大量のコードの安全なリファクタリングが可能になります。


コンパイラーは TypeScript の中心であり、型の正確性をチェックし、TypeScript コードを JavaScript に変換します。ただし、TypeScript の機能を最大限に活用するには、コンパイラーを正しく構成することが重要です。


各 TypeScript プロジェクトには、コンパイラーのすべての構成オプションを保持する 1 つ以上のtsconfig.jsonファイルがあります。


tsconfig の構成は、TypeScript プロジェクトで最適なタイプ セーフティと開発者エクスペリエンスを実現するための重要な手順です。関連するすべての重要な要素を時間をかけて慎重に検討することで、開発プロセスをスピードアップし、コードが堅牢でエラーがないことを保証できます。

標準構成の欠点

tsconfig のデフォルト構成により、開発者は TypeScript の利点の大部分を逃す可能性があります。これは、多くの強力な型チェック機能が有効にならないためです。 「デフォルト」構成とは、型チェック コンパイラ オプションが設定されていない構成を意味します。


例えば:


 { "compilerOptions": { "target": "esnext", "module": "esnext", "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "skipLibCheck": true, }, "include": ["src"] }


いくつかの主要な構成オプションがないと、主に 2 つの理由でコードの品質が低下する可能性があります。まず、TypeScript のコンパイラは、さまざまな場合にnullおよびundefined型を誤って処理する可能性があります。


次に、 any型がコードベースに制御不能に出現する可能性があり、この型に関する型チェックが無効になる可能性があります。


幸いなことに、これらの問題は、構成内のいくつかのオプションを調整することで簡単に修正できます。

ストリクトモード

{ "compilerOptions": { "strict": true } }


Strict モードは、幅広い型チェック動作を有効にすることで、プログラムの正確性をより強力に保証する必須の構成オプションです。


tsconfig ファイルで厳密モードを有効にすることは、最大限の型安全性とより良い開発者エクスペリエンスを実現するための重要なステップです。


tsconfig の構成には少し手間がかかりますが、プロジェクトの品質を向上させるのに大いに役立ちます。


strictコンパイラ オプションは、特にnoImplicitAnystrictNullChecksstrictFunctionTypesの strict モード ファミリ オプションをすべて有効にします。


これらのオプションは個別に設定することもできますが、いずれかをオフにすることはお勧めできません。その理由を例を見てみましょう。

暗黙的な任意の推論

{ "compilerOptions": { "noImplicitAny": true } }


any型は静的型システムの危険な抜け穴であり、これを使用するとすべての型チェック ルールが無効になります。その結果、バグが見逃されたり、コード エディターのヒントが適切に機能しなくなったりするなど、TypeScript のすべての利点が失われます。


any使用しても問題がないのは、極端な場合またはプロトタイピングが必要な場合のみです。最善の努力にもかかわらず、 any型が暗黙的にコードベースに侵入する可能性があります。


デフォルトでは、コンパイラは、 anyベースにエラーが発生しても、多くのエラーを許容します。具体的には、TypeScript を使用すると、型が自動的に推論できない場合でも、変数の型を指定しなくても済みます。


問題は、関数の引数などに変数の型を指定することをうっかり忘れてしまう可能性があることです。 TypeScript はエラーを表示する代わりに、変数の型をanyとして自動的に推測します。


 function parse(str) { // ^? any return str.split(''); } // TypeError: str.split is not a function const res1 = parse(42); const res2 = parse('hello'); // ^? any


noImplicitAnyコンパイラ オプションを有効にすると、コンパイラは変数の型がanyとして自動的に推論されるすべての場所を強調表示します。この例では、TypeScript は関数の引数の型を指定するように求めます。


 function parse(str) { // ^ Error: Parameter 'str' implicitly has an 'any' type. return str.split(''); }


型を指定すると、TypeScript は文字列パラメーターに数値を渡す際のエラーをすぐに検出します。変数res2に格納される関数の戻り値も正しい型になります。


 function parse(str: string) { return str.split(''); } const res1 = parse(42); // ^ Error: Argument of type 'number' is not // assignable to parameter of type 'string' const res2 = parse('hello'); // ^? string[]


Catch 変数の不明な型

{ "compilerOptions": { "useUnknownInCatchVariables": true } }


useUnknownInCatchVariablesを構成すると、try-catch ブロックで例外を安全に処理できるようになります。デフォルトでは、TypeScript は catch ブロック内のエラー タイプがanyであると想定するため、エラーに対して何でもできるようになります。


たとえば、キャッチしたエラーをそのまま、 Errorのインスタンスを受け入れるログ関数に渡すことができます。


 function logError(err: Error) { // ... } try { return JSON.parse(userInput); } catch (err) { // ^? any logError(err); }


ただし、実際には、エラーのタイプについての保証はなく、実行時にエラーが発生したときにのみ、その真のタイプを判断できます。ロギング関数がErrorではないものを受信すると、実行時エラーが発生します。


したがって、 useUnknownInCatchVariablesオプションは、エラーの種類をany 」からunknownに切り替えて、何かを行う前にエラーの種類を確認するように促します。


 try { return JSON.parse(userInput); } catch (err) { // ^? unknown // Now we need to check the type of the value if (err instanceof Error) { logError(err); } else { logError(new Error('Unknown Error')); } }


TypeScript は、 logError関数に渡す前にerr変数の型を確認するように求めるプロンプトを表示するため、より正確で安全なコードが得られます。残念ながら、このオプションは、 promise.catch()関数またはコールバック関数での入力エラーには役に立ちません。


ただし、 anyような場合の対処方法については、次の記事で説明します。

Call メソッドと apply メソッドの型チェック

{ "compilerOptions": { "strictBindCallApply": true } }


別のオプションは、 callおよびapplyを介したany内呼び出しの外観を修正します。これは最初の 2 つのケースほど一般的ではありませんが、考慮することが重要です。デフォルトでは、TypeScript はそのような構造の型をまったくチェックしません。


たとえば、関数の引数として何でも渡すことができ、最終的には常にany型を受け取ることになります。


 function parse(value: string) { return parseInt(value, 10); } const n1 = parse.call(undefined, '10'); // ^? any const n2 = parse.call(undefined, false); // ^? any


strictBindCallApplyオプションを有効にすると、TypeScript がよりスマートになるため、戻り値の型がnumberとして正しく推論されます。また、間違った型の引数を渡そうとすると、TypeScript はエラーを指摘します。


 function parse(value: string) { return parseInt(value, 10); } const n1 = parse.call(undefined, '10'); // ^? number const n2 = parse.call(undefined, false); // ^ Argument of type 'boolean' is not // assignable to parameter of type 'string'.


実行コンテキストの厳密な型

{ "compilerOptions": { "noImplicitThis": true } }


プロジェクト内でany出現を防ぐのに役立つ次のオプションは、関数呼び出しでの実行コンテキストの処理を修正します。 JavaScript の動的性質により、関数内のコンテキストのタイプを静的に決定することが困難になります。


デフォルトでは、TypeScript はそのような場合にコンテキストにany型を使用し、警告を出しません。


 class Person { private name: string; constructor(name: string) { this.name = name; } getName() { return function () { return this.name; // ^ 'this' implicitly has type 'any' because // it does not have a type annotation. }; } }


noImplicitThisコンパイラ オプションを有効にすると、関数のコンテキストの種類を明示的に指定するように求められます。このようにして、上記の例では、 Personクラスのnameフィールドではなく関数コンテキストにアクセスした際のエラーを捕捉できます。


TypeScript での Null および未定義のサポート

{ "compilerOptions": { "strictNullChecks": true } }


次に、 strictモードに含まれるいくつかのオプションでは、コードベースにany型が表示されません。ただし、TS コンパイラーの動作がより厳密になり、開発中により多くのエラーが見つかる可能性があります。


最初のこのようなオプションは、TypeScript でのnullundefinedの処理を修正します。デフォルトでは、TypeScript はnullundefinedどの型に対しても有効な値であると想定するため、予期しない実行時エラーが発生する可能性があります。


strictNullChecksコンパイラ オプションを有効にすると、開発者はnullundefined発生する可能性があるケースを明示的に処理する必要があります。


たとえば、次のコードを考えてみましょう。


 const users = [ { name: 'Oby', age: 12 }, { name: 'Heera', age: 32 }, ]; const loggedInUser = users.find(u => u.name === 'Max'); // ^? { name: string; age: number; } console.log(loggedInUser.age); // ^ TypeError: Cannot read properties of undefined


このコードはエラーなしでコンパイルされますが、「Max」という名前のユーザーがシステムに存在しない場合、実行時エラーがスローされる可能性があり、 users.find()undefined返します。これを防ぐには、 strictNullChecksコンパイラ オプションを有効にします。


TypeScript では、 users.find()によって返されるnullまたはundefined可能性を明示的に処理する必要があります。


 const loggedInUser = users.find(u => u.name === 'Max'); // ^? { name: string; age: number; } | undefined if (loggedInUser) { console.log(loggedInUser.age); }


nullundefiinedの可能性を明示的に処理することで、実行時エラーを回避し、コードをより堅牢でエラーのないものにすることができます。

厳密な関数の型

{ "compilerOptions": { "strictFunctionTypes": true } }


strictFunctionTypesを有効にすると、TypeScript のコンパイラがよりインテリジェントになります。バージョン 2.6 より前の TypeScript では、関数の引数の反変性がチェックされませんでした。これにより、関数が間違った型の引数で呼び出された場合、実行時エラーが発生します。


たとえば、関数型が文字列と数値の両方を処理できる場合でも、文字列のみを処理できる関数をその型に割り当てることができます。その関数に数値を渡すことはできますが、実行時エラーが発生します。


 function greet(x: string) { console.log("Hello, " + x.toLowerCase()); } type StringOrNumberFn = (y: string | number) => void; // Incorrect Assignment const func: StringOrNumberFn = greet; // TypeError: x.toLowerCase is not a function func(10);


幸いなことに、 strictFunctionTypesオプションを有効にするとこの動作が修正され、コンパイラはコンパイル時にこれらのエラーを検出して、関数の型の非互換性に関する詳細なメッセージを表示できるようになります。


 const func: StringOrNumberFn = greet; // ^ Type '(x: string) => void' is not assignable to type 'StringOrNumberFn'. // Types of parameters 'x' and 'y' are incompatible. // Type 'string | number' is not assignable to type 'string'. // Type 'number' is not assignable to type 'string'.


クラスプロパティの初期化

{ "compilerOptions": { "strictPropertyInitialization": true } }


最後に重要なことですが、 strictPropertyInitializationオプションを使用すると、値としてundefined含まない型の必須クラス プロパティの初期化をチェックできます。


たとえば、次のコードでは、開発者はemailプロパティを初期化するのを忘れています。デフォルトでは、TypeScript はこのエラーを検出しないため、実行時に問題が発生する可能性があります。


 class UserAccount { name: string; email: string; constructor(name: string) { this.name = name; // Forgot to assign a value to this.email } }


ただし、 strictPropertyInitializationオプションが有効になっている場合、TypeScript はこの問題を強調表示します。


 email: string; // ^ Error: Property 'email' has no initializer and // is not definitely assigned in the constructor.

安全なインデックス署名

{ "compilerOptions": { "noUncheckedIndexedAccess": true } }


noUncheckedIndexedAccessオプションはstrictモードの一部ではありませんが、プロジェクトのコード品質の向上に役立つ別のオプションです。これにより、インデックス アクセス式の戻り値の型がnullまたはundefinedであるかどうかをチェックできるようになり、実行時エラーを防ぐことができます。


キャッシュされた値を保存するためのオブジェクトがある次の例を考えてみましょう。次に、キーの 1 つの値を取得します。もちろん、目的のキーの値が実際にキャッシュに存在するという保証はありません。


デフォルトでは、TypeScript は値が存在し、その型がstringであると想定します。これにより、実行時エラーが発生する可能性があります。


 const cache: Record<string, string> = {}; const value = cache['key']; // ^? string console.log(value.toUpperCase()); // ^ TypeError: Cannot read properties of undefined


TypeScript でnoUncheckedIndexedAccessオプションを有効にするには、 undefined戻り値の型についてインデックス アクセス式をチェックする必要があります。これにより、実行時エラーを回避できます。これは、配列内の要素へのアクセスにも当てはまります。


 const cache: Record<string, string> = {}; const value = cache['key']; // ^? string | undefined if (value) { console.log(value.toUpperCase()); }

推奨される構成

説明したオプションに基づいて、タイプ セーフを最適化するために、プロジェクトのtsconfig.jsonファイルでstrictオプションとnoUncheckedIndexedAccessオプションを有効にすることを強くお勧めします。


 { "compilerOptions": { "strict": true, "noUncheckedIndexedAccess": true, } }


strictオプションをすでに有効にしている場合は、 strict: trueオプションの重複を避けるために、次のオプションを削除することを検討してください。


  • noImplicitAny
  • useUnknownInCatchVariables
  • strictBindCallApply
  • noImplicitThis
  • strictFunctionTypes
  • strictNullChecks
  • strictPropertyInitialization


また、型システムを弱めたり、実行時エラーを引き起こす可能性がある次のオプションを削除することをお勧めします。


  • keyofStringsOnly
  • noStrictGenericChecks
  • suppressImplicitAnyIndexErrors
  • suppressExcessPropertyErrors


これらのオプションを慎重に検討して構成することで、TypeScript プロジェクトで最適なタイプ セーフとより良い開発者エクスペリエンスを実現できます。

結論

TypeScript は、コンパイラと型システムを絶えず改良しながら、長い進化を遂げてきました。ただし、下位互換性を維持するために、TypeScript の構成はより複雑になり、型チェックの品質に大きな影響を与える可能性のある多くのオプションが追加されました。


これらのオプションを慎重に検討して構成することで、TypeScript プロジェクトで最適なタイプ セーフとより良い開発者エクスペリエンスを実現できます。どのオプションを有効にし、プロジェクト構成から削除するかを理解しておくことが重要です。


特定のオプションを無効にした場合の結果を理解すると、それぞれのオプションについて情報に基づいた決定を下せるようになります。


厳密に型指定すると結果が生じる可能性があることに留意することが重要です。 JavaScript の動的な性質に効果的に対処するには、単に変数の後に「数値」または「文字列」を指定するだけでなく、TypeScript についてよく理解する必要があります。


発生する型関連の問題をより効果的に解決するには、より複雑な構造と、ライブラリとツールの TypeScript ファーストのエコシステムに精通する必要があります。


そのため、コードを書くのにもう少し労力が必要になるかもしれませんが、私の経験に基づくと、長期的なプロジェクトではこの労力は価値があります。


この記事から何か新しいことを学んでいただければ幸いです。これはシリーズの最初の部分です。次の記事では、TypeScript の標準ライブラリの型を改善することで、より優れた型安全性とコード品質を実現する方法について説明します。ご期待ください。読んでいただきありがとうございます!

役立つリンク