TypeScript は、JavaScript 上に構築された厳密に型指定されたプログラミング言語であり、あらゆる規模で優れたツールを提供すると主張しています。ただし、 には 型が含まれており、多くの場合、暗黙的にコードベースに侵入し、TypeScript の利点の多くが失われる可能性があります。 TypeScript any この記事では、TypeScript プロジェクトで 型を制御する方法について説明します。 TypeScript のパワーを解き放ち、究極のタイプ セーフを実現し、コードの品質を向上させる準備をしましょう。 any TypeScript で Any を使用する場合の欠点 TypeScript は、開発者のエクスペリエンスと生産性を向上させるためのさまざまな追加ツールを提供します。 開発段階の早い段階でエラーを発見するのに役立ちます。 コード エディターと IDE に優れた自動補完機能を提供します。 素晴らしいコード ナビゲーション ツールと自動リファクタリングを通じて、大規模なコードベースのリファクタリングを簡単に行うことができます。 追加のセマンティクスと型による明示的なデータ構造を提供することで、コードベースの理解を簡素化します。 ただし、コードベースで 型を使用し始めるとすぐに、上記の利点がすべて失われます。 型は型システムの危険な抜け穴であり、これを使用すると、すべての型チェック機能と、型チェックに依存するすべてのツールが無効になります。その結果、バグが見逃されたり、コード エディターの有用性が低下したりするなど、TypeScript の利点がすべて失われます。 any any たとえば、次の例を考えてみましょう。 function parse(data: any) { return data.split(''); } // Case 1 const res1 = parse(42); // ^ TypeError: data.split is not a function // Case 2 const res2 = parse('hello'); // ^ any 上記のコードでは次のようになります。 関数内のオートコンプリートを見逃してしまいます。 エディターでは、 に使用できるメソッドについて正しい提案が表示されません。 parse data. data 最初のケースでは、 。 型チェックを無効にするため、TypeScript はエラーを強調表示できません。 TypeError: data.split is not a function any 2 番目のケースでは、 変数も 型を持ちます。これは、 を 1 回使用すると、コードベースの大部分に連鎖的な影響を与える可能性があることを意味します。 res2 any any 使用しても問題がないのは、極端な場合またはプロトタイピングが必要な場合のみです。一般に、TypeScript を最大限に活用するには、 使用も避けることをお勧めします。 any any Any 型の由来 明示的に記述することが唯一の選択肢ではないため、コードベース内の 型のソースを認識することが重要です。 型の使用を避けるための最善の努力にもかかわらず、any 型が暗黙的にコードベースに侵入する可能性があります。 any any any コードベースには、 タイプの主なソースが 4 つあります。 any tsconfig のコンパイラ オプション。 TypeScriptの標準ライブラリ。 プロジェクトの依存関係。 コードベース内での の明示的な使用。 any 最初の 2 つの点については 」と に関する記事をすでに書きました。プロジェクトのタイプ セーフティを改善したい場合は、ぜひチェックしてください。 、「tsconfig の主な考慮事項 「標準ライブラリの型の改善」 今回は、コードベース内の 型の外観を制御するための自動ツールに焦点を当てます。 any ステージ 1: ESLint の使用 、Web 開発者がベスト プラクティスとコードのフォーマットを確保するために使用する一般的な静的分析ツールです。これを使用して、コーディング スタイルを強制し、特定のガイドラインに準拠していないコードを見つけることができます。 ESLint は ESLint は、 プラグインのおかげで、TypeScript プロジェクトでも使用できます。おそらく、このプラグインはプロジェクトにすでにインストールされています。ただし、そうでない場合は、公式の 従うことができます。 typesctipt-eslint スタートガイドに の最も一般的な構成は次のとおりです。 typescript-eslint module.exports = { extends: [ 'eslint:recommended', 'plugin:@typescript-eslint/recommended', ], plugins: ['@typescript-eslint'], parser: '@typescript-eslint/parser', root: true, }; この構成により、 構文レベルで TypeScript を理解できるようになり、コード内で手動で記述された型に適用される単純な eslint ルールを作成できるようになります。たとえば、 明示的な使用を禁止できます。 eslint any プリセットには、コードの正確性を向上させることを目的とした、慎重に選択された ESLint ルールのセットが含まれています。プリセット全体を使用することをお勧めしますが、この記事では、 ルールのみに焦点を当てます。 recommended no-explicit-any 明示的なし TypeScript の厳密モードでは、暗黙的な の使用は禁止されていますが、明示的に 使用されることは妨げられません。 ルールは、コードベース内の任意の に手動で書き込むことを禁止するのに役立ちます。 any any no-explicit-any any // ❌ Incorrect function loadPokemons(): any {} // ✅ Correct function loadPokemons(): unknown {} // ❌ Incorrect function parsePokemons(data: Response<any>): Array<Pokemon> {} // ✅ Correct function parsePokemons(data: Response<unknown>): Array<Pokemon> {} // ❌ Incorrect function reverse<T extends Array<any>>(array: T): T {} // ✅ Correct function reverse<T extends Array<unknown>>(array: T): T {} このルールの主な目的は、チーム全体で の使用を禁止することです。これは、プロジェクト内で 使用は推奨されないというチームの合意を強化する手段です。 any any を 1 回使用しただけでも、 によりコードベースの重要な部分に連鎖的な影響を与える可能性があるため、これは重要な目標です。しかし、これは究極の型安全性の達成にはまだ程遠いです。 any 型推論 no-explicit-any では不十分な理由 明示的に使用される を扱いましたが、npm パッケージや TypeScript の標準ライブラリなど、プロジェクトの依存関係内には依然として多くの暗黙的な 存在します。 any any 次のコードを考えてみましょう。これはどのプロジェクトでも見られる可能性があります。 const response = await fetch('https://pokeapi.co/api/v2/pokemon'); const pokemons = await response.json(); // ^? any const settings = JSON.parse(localStorage.getItem('user-settings')); // ^? any 変数 と 両方に暗黙的に タイプが与えられました。この場合、 も TypeScript の strict モードも警告しません。まだ。 pokemons settings any no-explicit-any これは、 と の型が TypeScript の標準ライブラリから取得されており、これらのメソッドには明示的な 注釈が付いているために発生します。変数に対してより適切な型を手動で指定することはできますが、標準ライブラリには の型が 1,200 近く存在します。標準ライブラリからコードベースに侵入できる のケースを覚えておくことはほぼ不可能です。 response.json() JSON.parse() any any any 外部依存関係についても同様です。 npm には型指定が不十分なライブラリが多数あり、そのほとんどは依然として JavaScript で書かれています。結果として、このようなライブラリを使用すると、コードベース内に暗黙的な が大量に発生する可能性があります。 any 一般に、コードに侵入 方法はまだたくさんあります。 any ステージ 2: 型チェック機能の強化 理想的には、何らかの理由で 型を受け取った変数についてコンパイラにエラーを報告させるような設定を TypeScript に含めたいと考えています。残念ながら、そのような設定は現在存在せず、追加される予定もありません。 any この動作は、 プラグインの型チェック モードを使用することで実現できます。このモードは TypeScript と連携して動作し、TypeScript コンパイラから ESLint ルールに完全な型情報を提供します。この情報を使用すると、基本的に TypeScript の型チェック機能を拡張する、より複雑な ESLint ルールを作成できます。たとえば、ルールは、 取得方法に関係なく、 型のすべての変数を検索できます。 typescript-eslint any any 型認識ルールを使用するには、ESLint 構成をわずかに調整する必要があります。 module.exports = { extends: [ 'eslint:recommended', - 'plugin:@typescript-eslint/recommended', + 'plugin:@typescript-eslint/recommended-type-checked', ], plugins: ['@typescript-eslint'], parser: '@typescript-eslint/parser', + parserOptions: { + project: true, + tsconfigRootDir: __dirname, + }, root: true, }; の型推論を有効にするには、 を ESLint 構成に追加します。次に、 プリセットを に置き換えます。後者のプリセットには、約 17 個の新しい強力なルールが追加されます。この記事では、そのうちの 5 つにのみ焦点を当てます。 typescript-eslint parserOptions recommended recommended-type-checked 安全でない引数なし ルールは、 型の変数がパラメータとして渡される関数呼び出しを検索します。これが発生すると、型チェックが失われ、強い型付けの利点もすべて失われます。 no-unsafe-argument any たとえば、パラメータとしてオブジェクトを必要とする 関数を考えてみましょう。 JSON を受信して解析し、 型を取得したとします。 saveForm any // ❌ Incorrect function saveForm(values: FormValues) { console.log(values); } const formValues = JSON.parse(userInput); // ^? any saveForm(formValues); // ^ Unsafe argument of type `any` assigned // to a parameter of type `FormValues`. このパラメーターを指定して 関数を呼び出すと、 ルールによって安全でないというフラグが立てられ、 変数に適切な型を指定することが要求されます。 saveForm no-unsafe-argument value このルールは、関数の引数内のネストされたデータ構造を深く検査するのに十分強力です。したがって、関数の引数として渡すオブジェクトに型なしデータが含まれることは決してないと確信できます。 // ❌ Incorrect saveForm({ name: 'John', address: JSON.parse(addressJson), // ^ Unsafe assignment of an `any` value. }); このエラーを修正する最善の方法は、TypeScript の 、または や などの検証ライブラリを使用することです。たとえば、解析されたデータの正確な種類を絞り込む 関数を作成してみましょう。 型の絞り込み Zod Superstruct parseFormValues // ✅ Correct function parseFormValues(data: unknown): FormValues { if ( typeof data === 'object' && data !== null && 'name' in data && typeof data['name'] === 'string' && 'address' in data && typeof data.address === 'string' ) { const { name, address } = data; return { name, address }; } throw new Error('Failed to parse form values'); } const formValues = parseFormValues(JSON.parse(userInput)); // ^? FormValues saveForm(formValues); 安全上の懸念がないため、 受け入れる関数に引数として 型を渡すことができることに注意してください。 unknown any データ検証関数の作成は、特に大量のデータを扱う場合には、面倒な作業になることがあります。したがって、データ検証ライブラリの使用を検討する価値があります。たとえば、Zod の場合、コードは次のようになります。 // ✅ Correct import { z } from 'zod'; const schema = z.object({ name: z.string(), address: z.string(), }); const formValues = schema.parse(JSON.parse(userInput)); // ^? { name: string, address: string } saveForm(formValues); 安全でない割り当て ルールは、値が 型である変数割り当てを検索します。このような代入により、実際にはデータの型が異なる可能性があるにもかかわらず、コンパイラは変数に特定の型があると誤解される可能性があります。 no-unsafe-assignment any 前述の JSON 解析の例を考えてみましょう。 // ❌ Incorrect const formValues = JSON.parse(userInput); // ^ Unsafe assignment of an `any` value ルールのおかげで、 他の場所に渡す前でも、 型をキャッチできます。修正戦略は変わりません。型の絞り込みを使用して、変数の値に特定の型を提供できます。 no-unsafe-assignment formValues any // ✅ Correct const formValues = parseFormValues(JSON.parse(userInput)); // ^? FormValues 安全でないメンバーアクセスと安全でない呼び出しは禁止 これら 2 つのルールがトリガーされる頻度はかなり低くなります。ただし、私の経験に基づくと、不適切に型指定されたサードパーティの依存関係を使用しようとする場合、これらは非常に役立ちます。 ルールにより、変数が 型の場合、 または 可能性があるため、オブジェクト プロパティにアクセスできなくなります。 no-unsafe-member-access any null undefined ルールにより、 型の変数は関数ではない可能性があるため、関数として呼び出すことができなくなります。 no-unsafe-call any という不適切に型指定されたサードパーティ ライブラリがあると想像してみましょう。 untyped-auth // ❌ Incorrect import { authenticate } from 'untyped-auth'; // ^? any const userInfo = authenticate(); // ^? any ^ Unsafe call of an `any` typed value. console.log(userInfo.name); // ^ Unsafe member access .name on an `any` value. リンターは 2 つの問題を強調しています。 重要な引数を関数に渡し忘れる可能性があるため、 関数の呼び出しは安全ではない可能性があります。 authenticate オブジェクトから プロパティを読み取ることは、認証が失敗すると になるため、安全ではありません。 userInfo name null これらのエラーを修正する最善の方法は、厳密に型指定された API を備えたライブラリの使用を検討することです。ただし、これが不可能な場合は、 できます。固定ライブラリ タイプの例は次のようになります。 ライブラリの種類を自分で拡張 // ✅ Correct import { authenticate } from 'untyped-auth'; // ^? (login: string, password: string) => Promise<UserInfo | null> const userInfo = await authenticate('test', 'pwd'); // ^? UserInfo | null if (userInfo) { console.log(userInfo.name); } 危険なリターンはありません ルールは、より具体的なものを返す必要がある関数から誤って 型を返さないようにするのに役立ちます。このような場合、実際にはデータの型が異なる可能性があるにもかかわらず、コンパイラは戻り値が特定の型を持つと誤解する可能性があります。 no-unsafe-return any たとえば、JSON を解析し、2 つのプロパティを持つオブジェクトを返す関数があるとします。 // ❌ Incorrect interface FormValues { name: string; address: string; } function parseForm(json: string): FormValues { return JSON.parse(json); // ^ Unsafe return of an `any` typed value. } const form = parseForm('null'); console.log(form.name); // ^ TypeError: Cannot read properties of null 関数は、解析された値がチェックされないため、プログラムのどの部分でも使用されると実行時エラーが発生する可能性があります。 ルールにより、このような実行時の問題が防止されます。 parseForm no-unsafe-return この問題は、解析された JSON が期待される型と一致することを確認する検証を追加することで簡単に修正できます。今回は Zod ライブラリを使用してみましょう。 // ✅ Correct import { z } from 'zod'; const schema = z.object({ name: z.string(), address: z.string(), }); function parseForm(json: string): FormValues { return schema.parse(JSON.parse(json)); } パフォーマンスに関する注意事項 型チェックされたルールを使用すると、すべての型を推論するために TypeScript のコンパイラを呼び出す必要があるため、ESLint のパフォーマンスが低下します。この速度の低下は主に、コミット前フックおよび CI でリンターを実行するときに顕著ですが、IDE で作業している場合には目立ちません。型チェックは IDE の起動時に 1 回実行され、コードを変更すると型が更新されます。 型を推論するだけの方が、通常の コンパイラの呼び出しよりも高速に動作することに注意してください。たとえば、約 150 万行の TypeScript コードを含む最新のプロジェクトでは、 による型チェックに約 11 分かかりますが、ESLint の型認識ルールのブートストラップに必要な追加時間はわずか約 2 分です。 tsc tsc 私たちのチームにとって、型を認識した静的解析ルールを使用することによって得られる追加の安全性は、トレードオフの価値があります。小規模なプロジェクトでは、この決定はさらに簡単になります。 結論 TypeScript プロジェクトでの の使用を制御することは、最適なタイプ セーフティとコード品質を達成するために重要です。 プラグインを利用することで、開発者はコードベース内の 型の出現を特定して削除できるため、より堅牢で保守しやすいコードベースが得られます。 any typescript-eslint any 型を認識した eslint ルールを使用することにより、コードベース内にキーワード が出現しても、間違いや見落としではなく、意図的な決定となります。このアプローチにより、標準ライブラリやサードパーティの依存関係 でなく、独自のコード内での使用も防止されます。 any any 全体として、型認識リンターを使用すると、Java、Go、Rust などの静的型付けプログラミング言語と同様のレベルの型安全性を達成できます。これにより、大規模プロジェクトの開発とメンテナンスが大幅に簡素化されます。 この記事から何か新しいことを学んでいただければ幸いです。読んでくれてありがとう! 役立つリンク ライブラリ: typescript-eslint 記事: tsconfig における重要な考慮事項 記事: 標準ライブラリの型の改善 ドキュメント: TypeScript での型の絞り込み ドキュメント: TypeScript での型推論 図書館:ゾッド ライブラリ: スーパーストラクト