TypeScriptは単独で存在するものではありません。JavaScriptのエコシステムを念頭に置いて構築されており、今日多くのJavaScriptが存在します。JavaScriptコードベースをTypeScriptに変換することは、多少面倒ではありますが、通常は困難ではありません。このチュートリアルでは、どのように始めるかを見ていきます。新しいTypeScriptコードを書くのに十分なハンドブックを読んでいることを前提としています。
Reactプロジェクトを変換したい場合は、最初にReact変換ガイドを参照することをお勧めします。
ディレクトリの設定
プレーンなJavaScriptで記述している場合、.jsファイルがsrc、lib、またはdistディレクトリにあり、必要に応じて実行されるように、JavaScriptを直接実行している可能性があります。
その場合、記述したファイルはTypeScriptへの入力として使用され、TypeScriptが生成する出力を実行します。JSからTSへの移行中、TypeScriptが上書きするのを防ぐために、入力ファイルを分離する必要があります。出力ファイルを特定のディレクトリに配置する必要がある場合は、それがあなたの出力ディレクトリになります。
バンドルやBabelのような別のトランスパイラを使用するなど、JavaScriptでいくつかの中間ステップを実行している場合もあります。この場合、このようなフォルダー構造が既に設定されている可能性があります。
ここからは、ディレクトリが次のように設定されていると仮定します。
projectRoot ├── src │ ├── file1.js │ └── file2.js ├── built └── tsconfig.json
srcディレクトリの外にtestsフォルダーがある場合は、srcに1つのtsconfig.json、testsにも1つのtsconfig.jsonがある場合があります。
設定ファイルの作成
TypeScriptは、含めるファイルや実行するチェックの種類など、プロジェクトのオプションを管理するためにtsconfig.jsonというファイルを使用します。プロジェクトの最小限の設定ファイルを作成しましょう。
json{"compilerOptions": {"outDir": "./built","allowJs": true,"target": "es5"},"include": ["./src/**/*"]}
ここでは、TypeScriptにいくつかのことを指定しています。
srcディレクトリ(includeを使用)で理解できるファイルをすべて読み込みます。- JavaScriptファイルを入力として受け入れます(
allowJsを使用)。 builtにすべての出力ファイルを生成します(outDirを使用)。- 新しいJavaScript構造をECMAScript 5のような古いバージョンに変換します(
targetを使用)。
この時点で、プロジェクトのルートでtscを実行しようとすると、builtディレクトリに出力ファイルが表示されるはずです。builtのファイルのレイアウトは、srcのレイアウトと同一に見えるはずです。これで、TypeScriptがプロジェクトで動作するようになりました。
初期の利点
この時点でも、TypeScript がプロジェクトを理解することで、いくつかの大きなメリットが得られます。 VS Code や Visual Studio のようなエディターを開くと、補完のようなツールサポートが頻繁に利用できるようになっていることに気づくでしょう。 また、次のようなオプションを使用して、特定のバグをキャッチすることもできます。
noImplicitReturnsは、関数の最後に return を忘れないようにするのに役立ちます。noFallthroughCasesInSwitchは、switchブロック内のcase間でbreakステートメントを忘れたくない場合に役立ちます。
TypeScript は、到達不能なコードやラベルについても警告します。これらはそれぞれ allowUnreachableCode および allowUnusedLabels で無効にできます。
ビルドツールとの統合
パイプラインには、さらにビルドステップが必要になる場合があります。 たとえば、ファイルのそれぞれに何かを連結する場合などです。 ビルドツールはそれぞれ異なりますが、要点を説明するように最善を尽くします。
Gulp
何らかの形で Gulp を使用している場合は、TypeScript で Gulp を使用する方法や、Browserify、Babelify、Uglify などの一般的なビルドツールと統合する方法に関するチュートリアルがあります。 詳細については、そちらを参照してください。
Webpack
Webpack の統合は非常に簡単です。 TypeScript ローダーである ts-loader と、デバッグを容易にする source-map-loader を組み合わせて使用できます。 次のコマンドを実行するだけです。
shellnpm install ts-loader source-map-loader
また、次のオプションを webpack.config.js ファイルにマージします。
jsmodule.exports = {entry: "./src/index.ts",output: {filename: "./dist/bundle.js",},// Enable sourcemaps for debugging webpack's output.devtool: "source-map",resolve: {// Add '.ts' and '.tsx' as resolvable extensions.extensions: ["", ".webpack.js", ".web.js", ".ts", ".tsx", ".js"],},module: {rules: [// All files with a '.ts' or '.tsx' extension will be handled by 'ts-loader'.{ test: /\.tsx?$/, loader: "ts-loader" },// All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'.{ test: /\.js$/, loader: "source-map-loader" },],},// Other options...};
ts-loader は、.js ファイルを処理する他のローダーよりも前に実行する必要があることに注意してください。
Webpack の使用例については、React と Webpack に関するチュートリアルをご覧ください。
TypeScript ファイルへの移行
この時点で、おそらく TypeScript ファイルの使用を開始する準備ができているでしょう。 最初のステップは、.js ファイルの 1 つを .ts にリネームすることです。 ファイルで JSX を使用する場合は、.tsx にリネームする必要があります。
そのステップは完了しましたか? 素晴らしい! JavaScript から TypeScript へのファイルの移行が完了しました!
もちろん、それは正しくないように感じるかもしれません。 TypeScript をサポートするエディターでそのファイルを開く場合 (または tsc --pretty を実行する場合)、特定の行に赤い波線が表示される可能性があります。 これらは、Microsoft Word などのエディターの赤い波線と同じように考える必要があります。 Word がドキュメントの印刷を許可するように、TypeScript は引き続きコードを変換します。
それが緩すぎると感じる場合は、その動作を厳しくすることができます。 たとえば、エラーが発生した場合に TypeScript が JavaScript にコンパイルすることを望まない場合は、noEmitOnError オプションを使用できます。 その意味で、TypeScript には厳密さのダイヤルがあり、そのつまみを必要なだけ高くすることができます。
利用可能なより厳密な設定を使用する予定の場合は、今すぐオンにすることをお勧めします (下記「より厳密なチェックの取得」を参照)。 たとえば、型について明示的に指示せずに TypeScript が any を暗黙的に推論することを絶対に望まない場合は、ファイルの変更を開始する前に noImplicitAny を使用できます。 やや圧倒されるように感じるかもしれませんが、長期的なメリットははるかに早く明らかになります。
エラーの除去
すでに述べたように、変換後にエラーメッセージが表示されても驚くことではありません。 重要なことは、これらのエラーを実際に 1 つずつ確認し、エラーに対処する方法を決定することです。 多くの場合、これらは正当なバグですが、TypeScript に対して実行しようとしていることをもう少し詳しく説明する必要がある場合もあります。
モジュールからのインポート
Cannot find name 'require'. や Cannot find name 'define'. のようなエラーが多数発生する可能性があります。 このような場合、モジュールを使用している可能性が高くなります。 これらの存在を TypeScript に納得させるために、次のように記述することもできますが、
ts// For Node/CommonJSdeclare function require(path: string): any;
または
ts// For RequireJS/AMDdeclare function define(...args: any[]): any;
これらの呼び出しを削除して、インポートに TypeScript の構文を使用することをお勧めします。
まず、TypeScript の module オプションを設定して、モジュールシステムを有効にする必要があります。 有効なオプションは、commonjs、amd、system、および umd です。
次の Node/CommonJS コードがある場合
jsvar foo = require("foo");foo.doStuff();
または次の RequireJS/AMD コードがある場合
jsdefine(["foo"], function (foo) {foo.doStuff();});
次の TypeScript コードを記述します。
tsimport foo = require("foo");foo.doStuff();
宣言ファイルの取得
TypeScript インポートへの変換を開始すると、Cannot find module 'foo'. のようなエラーが発生する可能性があります。 ここでの問題は、ライブラリを記述するための宣言ファイルがない可能性があることです。 幸いなことに、これは非常に簡単です。 TypeScript が lodash のようなパッケージについて不満を言う場合は、次のように記述するだけで済みます。
shellnpm install -S @types/lodash
commonjs 以外のモジュールオプションを使用している場合は、moduleResolution オプションを node に設定する必要があります。
その後、問題なく lodash をインポートして、正確な補完を取得できるようになります。
モジュールからのエクスポート
通常、モジュールからのエクスポートには、exports や module.exports のような値にプロパティを追加することが含まれます。 TypeScript では、トップレベルのエクスポートステートメントを使用できます。 たとえば、次のように関数をエクスポートした場合、
jsmodule.exports.feedPets = function (pets) {// ...};
次のように書き出すことができます。
tsexport function feedPets(pets) {// ...}
exports オブジェクトを完全に上書きする場合もあります。 これは、このスニペットのようにモジュールをすぐに呼び出し可能にするために人々がよく使用するパターンです。
jsvar express = require("express");var app = express();
以前は次のように記述していた可能性があります。
jsfunction foo() {// ...}module.exports = foo;
TypeScript では、export = 構造でこれをモデル化できます。
tsfunction foo() {// ...}export = foo;
引数が多すぎる/少なすぎる
多すぎる/少なすぎる引数で関数を呼び出していることがあります。 通常、これはバグですが、場合によっては、パラメーターを記述する代わりに arguments オブジェクトを使用する関数を宣言している場合があります。
jsfunction myCoolFunction() {if (arguments.length == 2 && !Array.isArray(arguments[1])) {var f = arguments[0];var arr = arguments[1];// ...}// ...}myCoolFunction(function (x) {console.log(x);},[1, 2, 3, 4]);myCoolFunction(function (x) {console.log(x);},1,2,3,4);
この場合、関数オーバーロードを使用して、myCoolFunction をどのように呼び出すことができるかを呼び出し元に伝えるために TypeScript を使用する必要があります。
tsfunction myCoolFunction(f: (x: number) => void, nums: number[]): void;function myCoolFunction(f: (x: number) => void, ...nums: number[]): void;function myCoolFunction() {if (arguments.length == 2 && !Array.isArray(arguments[1])) {var f = arguments[0];var arr = arguments[1];// ...}// ...}
myCoolFunction に2つのオーバーロードシグネチャを追加しました。1つ目は、myCoolFunction が関数(number を引数にとる)と、number のリストを引数にとることを示しています。2つ目は、同じく関数を引数にとり、その後に可変長引数 (...nums) を使用して、任意の数の引数が number である必要があることを示しています。
順次追加されるプロパティ
オブジェクトを作成し、その後すぐにプロパティを追加することを、より美的に好む人もいます。
jsvar options = {};options.color = "red";options.volume = 11;
TypeScript は、options の型を最初にプロパティを持たない {} として推論するため、color と volume への代入はできないと表示します。代わりに、宣言をオブジェクトリテラル自体の中に移動すれば、エラーは発生しません。
tslet options = {color: "red",volume: 11,};
options の型を定義し、オブジェクトリテラルに型アサーションを追加することもできます。
tsinterface Options {color: string;volume: number;}let options = {} as Options;options.color = "red";options.volume = 11;
あるいは、options の型を any とすることもできます。これは最も簡単な方法ですが、得られるメリットは最も少なくなります。
any、Object、および {}
Object はほとんどの場合、最も一般的な型であるため、値が任意のプロパティを持つことができることを示すために Object または {} を使用したくなるかもしれません。しかし、このような状況では、実際には any が使用したい型です。なぜなら、それが最も柔軟な型だからです。
たとえば、Object 型の何かがある場合、それに対して toLowerCase() のようなメソッドを呼び出すことはできません。一般的に、より一般的な型は、型に対してできることが少ないことを意味しますが、any は特別な型であり、最も一般的な型でありながら、それに対して何でも行うことができます。つまり、呼び出し、構築、プロパティへのアクセスなどを行うことができます。ただし、any を使用すると、TypeScript が提供するエラーチェックやエディターサポートのほとんどが失われることに注意してください。
Object と {} のどちらかを選択する必要がある場合は、{} を優先する必要があります。それらはほとんど同じですが、技術的には {} は特定の特殊なケースにおいて Object よりも一般的な型です。
より厳密なチェック
TypeScript には、プログラムの安全性と分析を向上させるための特定のチェック機能が用意されています。コードベースを TypeScript に変換したら、これらのチェックを有効にして、安全性を高めることができます。
暗黙的な any なし
TypeScript が特定の型を判断できない場合があります。できる限り寛容であるために、代わりに any 型を使用することにします。これは移行には最適ですが、any を使用すると型安全性が失われ、他の場所で得られるようなツールサポートが得られません。 noImplicitAny オプションを使用すると、TypeScript にこれらの場所を特定させ、エラーを生成させることができます。
厳密な null および undefined チェック
デフォルトでは、TypeScript は null と undefined がすべての型の範囲内にあると想定します。つまり、number 型で宣言されたものは null または undefined になる可能性があります。null と undefined は JavaScript と TypeScript で非常に頻繁にバグの原因となるため、TypeScript には strictNullChecks オプションがあり、これらの問題を心配する必要性を軽減できます。
strictNullChecks を有効にすると、null と undefined はそれぞれ null と undefined という独自の型を持ちます。何かが可能性として null である場合は常に、元の型と共用体型を使用できます。たとえば、何かが number または null になりうる場合、型を number | null と記述します。
TypeScript が null/undefined の可能性があると判断している値があり、実際にはそうではない場合は、後置 ! 演算子を使用すると、そうではないことを伝えることができます。
tsdeclare var foo: string[] | null;foo.length; // error - 'foo' is possibly 'null'foo!.length; // okay - 'foo!' just has type 'string[]'
注意点として、strictNullChecks を使用する場合は、依存関係も strictNullChecks を使用するように更新する必要がある場合があります。
this に対する暗黙的な any なし
クラスの外で this キーワードを使用すると、デフォルトで any 型になります。たとえば、Point クラスがあり、メソッドとして追加したい関数があるとします。
tsclass Point {constructor(public x, public y) {}getDistance(p: Point) {let dx = p.x - this.x;let dy = p.y - this.y;return Math.sqrt(dx ** 2 + dy ** 2);}}// ...// Reopen the interface.interface Point {distanceFromOrigin(): number;}Point.prototype.distanceFromOrigin = function () {return this.getDistance({ x: 0, y: 0 });};
これには、上で述べたのと同じ問題があります。getDistance をスペルミスしてもエラーが発生しない可能性があります。このため、TypeScript には noImplicitThis オプションがあります。このオプションを設定すると、this が明示的な (または推論された) 型なしで使用されている場合に、TypeScript はエラーを発行します。修正方法は、インターフェースまたは関数自体に this パラメーターを使用して、明示的な型を与えることです。
tsPoint.prototype.distanceFromOrigin = function (this: Point) {return this.getDistance({ x: 0, y: 0 });};