問題の歴史:
TypeScriptは既存のJSアプリケーションに静的型付けを追加するために設計されています。外部のモジュールや純粋なJavaScriptで書かれた自分のモジュールをインポートする際、どのように型付けを行うべきかという問題が生じます。この場合、TypeScriptはいわゆる宣言ファイル(.d.ts)を使用するか、自動的に型を推論します(時には誤って)。
問題点:
インポート時に適切な型の記述が見つからない場合、変数はany型となり、これは型安全性の完全な喪失を意味します。これはコンパイラが見逃すエラーやランタイムのバグを引き起こす可能性があります。多くの場合、開発者はインポートされた関数やオブジェクトの型を明示的に宣言することを忘れがちです。
解決策:
コード例:
// 1. JSモジュールのインポートの明示的な型付け import myFunc from './myLib'; declare function myFunc(x: number): boolean; // 2. myLib.d.tsファイルがあり、export function myFunc(x: number): boolean;が定義されたJSモジュールのインポート import { myFunc } from './myLib'; // 3. 型付けなしでモジュールをインポートし、明示的に型を記述 import * as legacy from './legacy'; const typedLegacy: { runTask: (name: string) => void } = legacy;
重要な特徴:
TypeScriptは、宣言がないインポートされたJSモジュールの型を自動的に推論できますか?
いいえ、ファイルがJavaScriptで書かれている場合、型の宣言がない限り、TypeScriptはanyを仮定し、型に関する情報を失います。 trivialなケースを除いて(export const x = 1;)。
JSモジュールに新しいフィールドが追加された場合、インポートされた型を「拡張」できますか?
宣言ファイル(.d.ts)を更新する場合のみ可能です。型が.d.tsに固定されている場合、TypeScriptはそれを「真実」として使用し、新しいフィールドは型付けされないか、エラーになります。
宣言や@types/なしで外部JSモジュールをTypeScriptプロジェクトにインポートするのは安全ですか?
いいえ、これは安全性を大幅に低下させます — インポート作業がuntyped(any)になり、コンパイラはエラーを出力せず、モジュールが利用できないか、APIが変更されてもエラーは発生しません。このようなモジュールの使用は、一時的な解決策としてのみ、明示的な型付けまたはコードの隔離と共に許可されます。
開発者は宣言なしの外部JSライブラリをインポートし、APIを自信を持って使用しており、型はanyです。ライブラリが更新されるとメソッドのシグネチャが変更されますが、エラーは発生せず、ランタイムにバグが発生します。
利点:
欠点:
.d.ts宣言ファイルが作成されるか、@types/パッケージが追加され、APIの説明は元のJSモジュールに厳密に一致しています。すべてのインポートされたメソッドが型付けされており、IDEが自動補完を支援し、いかなる不一致もコンパイルエラーを示します。
利点:
欠点: