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
を組み合わせて使用できます。 次のコマンドを実行するだけです。
shell
npm install ts-loader source-map-loader
また、次のオプションを webpack.config.js
ファイルにマージします。
js
module.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 コードがある場合
js
var foo = require("foo");foo.doStuff();
または次の RequireJS/AMD コードがある場合
js
define(["foo"], function (foo) {foo.doStuff();});
次の TypeScript コードを記述します。
ts
import foo = require("foo");foo.doStuff();
宣言ファイルの取得
TypeScript インポートへの変換を開始すると、Cannot find module 'foo'.
のようなエラーが発生する可能性があります。 ここでの問題は、ライブラリを記述するための宣言ファイルがない可能性があることです。 幸いなことに、これは非常に簡単です。 TypeScript が lodash
のようなパッケージについて不満を言う場合は、次のように記述するだけで済みます。
shell
npm install -S @types/lodash
commonjs
以外のモジュールオプションを使用している場合は、moduleResolution
オプションを node
に設定する必要があります。
その後、問題なく lodash をインポートして、正確な補完を取得できるようになります。
モジュールからのエクスポート
通常、モジュールからのエクスポートには、exports
や module.exports
のような値にプロパティを追加することが含まれます。 TypeScript では、トップレベルのエクスポートステートメントを使用できます。 たとえば、次のように関数をエクスポートした場合、
js
module.exports.feedPets = function (pets) {// ...};
次のように書き出すことができます。
ts
export function feedPets(pets) {// ...}
exports オブジェクトを完全に上書きする場合もあります。 これは、このスニペットのようにモジュールをすぐに呼び出し可能にするために人々がよく使用するパターンです。
js
var express = require("express");var app = express();
以前は次のように記述していた可能性があります。
js
function foo() {// ...}module.exports = foo;
TypeScript では、export =
構造でこれをモデル化できます。
ts
function foo() {// ...}export = foo;
引数が多すぎる/少なすぎる
多すぎる/少なすぎる引数で関数を呼び出していることがあります。 通常、これはバグですが、場合によっては、パラメーターを記述する代わりに arguments
オブジェクトを使用する関数を宣言している場合があります。
js
function 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 を使用する必要があります。
ts
function 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
である必要があることを示しています。
順次追加されるプロパティ
オブジェクトを作成し、その後すぐにプロパティを追加することを、より美的に好む人もいます。
js
var options = {};options.color = "red";options.volume = 11;
TypeScript は、options
の型を最初にプロパティを持たない {}
として推論するため、color
と volume
への代入はできないと表示します。代わりに、宣言をオブジェクトリテラル自体の中に移動すれば、エラーは発生しません。
ts
let options = {color: "red",volume: 11,};
options
の型を定義し、オブジェクトリテラルに型アサーションを追加することもできます。
ts
interface 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
の可能性があると判断している値があり、実際にはそうではない場合は、後置 !
演算子を使用すると、そうではないことを伝えることができます。
ts
declare 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
クラスがあり、メソッドとして追加したい関数があるとします。
ts
class 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
パラメーターを使用して、明示的な型を与えることです。
ts
Point.prototype.distanceFromOrigin = function (this: Point) {return this.getDistance({ x: 0, y: 0 });};