JavaScriptプロジェクトでTypeScriptを導入する際には、“柔らかい”静的型付き言語とするのがおすすめです。藤吾郎（gfx）さんがまとめた「がんばらないTypeScript」のガイドラインです。

TypeScriptは、すべてのJavaScriptプロジェクトで採用する価値のある技術です。TypeScriptとこれに対応したエディタを導入することで、補完や型ベースの整合性のチェックにより、すべてのプロジェクトで生産性が上がります。またリファクタリングも容易になるので、長期あるいは大規模なプロジェクトでも品質を保ちやすくなります。

この記事では、TypeScriptについて最低限の知識とともに、サクッと （どちらかというと既存のプロジェクトに） 導入するためのノウハウや手順を、筆者 （@__gfx__） の経験もあわせて解説していきます。

ただし、TypeScriptのユースケースは、Webフロントエンド、モバイルアプリ、サーバーサイドアプリなど多岐にわたります。そこでこの記事では、以下のようなプロジェクトを想定して、設定などを具体的に見ていきます。

Webフロントエンドのビルドにwebpackを使う

ES modulesまたはCommonJS準拠のモジュールシステムを使う

※本記事は、TypeScript v3.4（2019年3月リリース）に基づいています。

TypeScriptとは何か？ がんばらないTypeScriptとは何か？

あらためて「TypeScriptとは何か？」から始めましょう。TypeScriptは、静的型付きプログラミング言語です。特徴としては、ほとんどの言語機能をJavaScriptから継承していることです。TypeScriptのコードはJavaScriptに変換 （コンパイル*1） され、最終的にJavaScriptエンジンで実行されます。

これは重要なことなので何度も繰り返しますが、実際、TypeScriptはほとんどJavaScriptです。例えば、公式サイトのうたい文句は“JavaScript that scales”です。“scales”というのは、ここでは「大規模開発に耐えられる」といった意味ですから、「大規模開発に耐えられるJavaScript」ということです。

JavaScriptにコンパイルされる言語はたくさんありますが、TypeScriptはコンパイル前と後のコードにほとんど違いがありません。「TypeScriptはほとんどJavaScriptである」というのは文字通りの意味で、JavaScriptの知識はすべてそのままTypeScriptに応用できます。

これに対して、CoffeeScriptやDartといったJavaSciptと大きく異なる言語機能を持つaltJSでは、コンパイル後のコードは、元のコードとも自然なJavaScriptとも異なるものになります。その良し悪しはともかく、JavaScriptと異なる言語機能を持つこれらのaltJSには、JavaScriptとは異なる知識体系が必要ということです。

ところで、一般的には、静的型付き言語は動的型付き言語より高速であるといわれます。つまり、静的な型 （型注釈） はCPUのための型でもあると考えられています。しかし、TypeScriptはJavaScriptと実行速度が変わりません。つまり、TypeScriptの型注釈は、人にとってはコメントであり、またエディタなどのツールにとっては補完などのコーディングサポートに使われる情報でもありますが、実行には何の影響もないのです。

TypeScript導入の可否

ところで2019年のいま、TypeScriptは導入すべきでしょうか。これについては「導入すれば得るものが多い」という評価がほぼ定まっていると考えてよいでしょう。「『TypeScriptを導入すべきか』で悩む時代は既に終わっている」という意見もあります。

JavaScriptプロジェクトへの導入も、コンパイルを通すまでなら、早ければ数分で済みます。一通り動作確認して完成させるまででも、数日程度で済むはずです。

試しに、この記事のためReact公式サイトで紹介されているReactアプリをいくつかTypeScript化してみました。作業時間としては、インストールの待ち時間を除けば、それぞれ10分程度で変換して動作確認までできました。

アプリ 差分（プルリクエスト） wc -l Calculator gfx/calculator/pull/1 461行 Pokedex gfx/pokedex/pull/1 267行

もっとも、多少のハマりどころがないわけではありません。特に、TypeScriptを導入する際の設定は知識が必要です。また、どのようにTypeScriptが実行されるかという一連の流れの理解につまずく人が多いようです。本記事では、そういったハマりどころを集中して解説していこうと思います。

難しい型システムをマスターしなくてもTypeScriptで開発できる

TypeScriptは、JavaScriptに静的型という大いなる力を与える言語です。そしてその「静的型の堅さ」もかなり自由に設定できます。strictにするオプションをすべて有効にして「堅い」静的型付き言語として振る舞うこともできれば、その逆にJavaScriptに毛が生えた程度の「柔らかい」静的型付き言語として振る舞えもします。

ところで、TypeScriptは型や設定が複雑で難しく学習コストが高いという意見をよく目にします。実際のところ、TypeScriptの型システムをマスターするのが難しいというのは事実です。設定ファイル（ tsconfig.json ）の項目も多く、しかも「静的型の堅さ」について最初から適切な判断をしなければならないような気がして、敷居が高く感じてしまうのも仕方がありません。

しかし、型システムをマスターしなければTypeScriptで開発できないわけではありません。複雑な型演算*2は、アプリケーションを書くときに必要になることはほとんどありません。またほかの多くの静的型付き言語と異なり、型エラーで困ったときはいつでも any で握りつぶせます。動くことが分かっているコードで遭遇する型エラーを握りつぶすのは、特にJavaScriptをTypeScriptに置き換えるフェーズでは、ためらわずにやる方がよいと思っています。

設定ファイルについても同じように、最初からすべての型チェックのオプションを有効にするべきとは思いません。いくつかの型チェックは、少なくともTypeScriptに不慣れな間は、無効にする方が生産性が上がるはずです。

「がんばらないTypeScript」というガイドライン

この記事では、以下のガイドラインに適合するTypeScriptのスタイルを「がんばらないTypeScript」スタイルと定義します。

設定ファイルをがんばらない tsconfig.json ではオプトインの型チェックオプションを任意で無効化してよいものとする

ではオプトインの型チェックオプションを任意で無効化してよいものとする 特に noImplicityAny は無効化を奨励する 型付けをがんばらない 型注釈はあとから足せばよいと割り切る

コードにおいてはいつでも any で型エラーを握りつぶしてよい 型定義ファイルをがんばらない 型定義の提供されていないライブラリは型がないまま使う

DefinitelyTyped （ @types ） はしばしば「まともなものがあればラッキー」くらいに割り切る

それぞれについての詳細は後述します。

この「がんばらない」は、「自分でもがんばらないし、チームメイトにもがんばりを求めない」、例えばコードレビューでは厳密な型注釈を求めないということです。チームで運用するとなれば、そのチームでコンセンサスを得る必要もあるでしょう。

「がんばらないTypeScript」は、TypeScriptの設定や型の厳格な運用に時間を使うのではなく、プロダクトの開発に時間を使うべきという思想です。TypeScriptに慣れていないときは特にそうで、型付けは慣れてからする方がずっと少ない時間で正確にできるはずです。

そして、たとえTypeScriptについてがんばらないとしても、TypeScriptには型推論があるため型チェックよる恩恵は十分あります。TypeScriptの恩恵をほどほどに得つつプロダクト開発の生産性を上げていこう、というのが「がんばらないTypeScript」の目指す道です。

なお、この「がんばらないTypeScript」というガイドラインの名称は、@t_snzkさんのブログ記事から拝借しました。この記事の「がんばらないTypeScript」の定義も、基本的にはこのエントリのものと互換性があります。

「がんばらないTypeScript」で得られるもの

ところで「がんばらないTypeScript」の話をすると、よく「それではTypeScriptを導入する意味がないのではないか」といわれます。しかし、もちろんそんなことはありません。

TypeScriptは、すべてのstrict系オプションを無効にした状態でさえ、JavaScriptよりはずっと厳格です。例えば、以下のコードはJavaScriptでは実行可能で無意味な値（ NaN ）を出力しますが、TypeScriptだと最も緩いデフォルトの設定でもコンパイルは通りません。

let foo = "foo" ; foo ++; console.log ( foo );

コンパイルすると、次のようなエラーになります。

$ npx ts-node foo-increments.ts ⨯ Unable to compile TypeScript: add.ts: 4 : 1 - error TS2356: An arithmetic operand must be of type ' any ' , ' number ' , ' bigint ' or an enum type. 4 foo++; ~~~

この出力は「数値演算は any 、 number 、 bigint またはenum typeに対してのみ行えます」という意味です。

これは、TypeScriptだと let foo = "foo" と宣言したとき変数 foo が型推論によって文字列型になり、文字列型にインクリメント演算子（ ++ ）は適用できないためです。

このほか、ECMAScriptの標準ライブラリの型定義や標準DOM APIの型定義がもともと提供されていることもあり、「がんばらないTypeScript」の設定のもとで必須でない型注釈をすべて省略したとしても、組み込み型のための型チェックとコード補完により、JavaScriptよりはるかに開発しやすいことでしょう。

もちろん、TypeScriptに習熟するにつれて、徐々に厳格にしていくことは可能ですし、そうした方がよいとは思います。しかし、最初からTypeScriptをマスターする必要はありませんし、またマスターしていなくても、型チェックの恩恵は十分にあります。

TypeScriptによるWebアプリケーションの開発

それでは実際に、TypeScriptの設定を見てみます。最初に説明したように、次のような条件のWebアプリを想定しています。

Webフロントエンドのビルドにwebpackを使う

ES modulesまたはCommonJS準拠のモジュールシステムを使う

これらに必要なツールチェインとTypeScriptの考え方を見ていきます。

TypeScriptのコンパイラ

TypeScriptのコンパイラは、npmモジュールとして配布されています。TypeScriptコンパイラはTypeScriptで書かれており、TypeScript APIを使ったサードパーティ製のカスタムコンパイラも数多くあります。

このモジュールにはコンパイラである tsc コマンドと、language serviceを提供する tsserver コマンドが入っています。 tsserver はエディタなどのためのlanguage service*3を利用するツールが使うものです。

また、ちょっとしたTypeScriptコードをすぐ試したいときやテストの実行などに便利な ts-node というコマンドがnpmモジュールとして提供されています。 ts-node はTypeScriptのカスタムコンパイラです。

ts-node - npm

使用例は次のようになります。

$ npx ts-node -e 'let a = "foo"; a++' [eval].ts:1:16 - error TS2356: An arithmetic operand must be of type 'any', 'number', 'bigint' or an enum type. 1 let a = "foo"; a++ ~

webpackでTypeScriptを使うときは、 ts-loader を利用します。これもTypeScript APIを使ったカスタムコンパイラです。

ts-loader - npm

エディタはlanguage serviceをサポートしたものを

エディタは、必ずTypeScriptのlanguage serviceをサポートしたものを使ってください。TypeScriptのlanguage serviceは非常に強力なので、これが使えないとTypeScriptを使う利点は半減します。

おすすめは、Visual Studio Code （vscode） です。

筆者は、RubyMine （IntelliJ IDEA系列のIDE） もよく使います。

このいずれも追加のプラグインなしで、TypeScriptのlanguage serviceを利用できます。

シンプルなTypeScript環境でスクリプトを実行してみる

webpackの設定に入る前に、まずシンプルなTypeScript実行環境を作って試しましょう。

手順は次のようになります。リポジトリ名は hello-typescript です。

mkdir hello-typescript cd hello-typescript npm init -y npm add -D typescript ts-node @types/node core-js npx tsc --init echo ' node_modules/

*.js

package-lock.json ' > .gitignore git init && git add . && git commit -m " initial commit "

tsc --init は、TypeScriptの設定ファイルである tsconfig.json を生成します。

このデフォルトで生成される tsconfig.json 自体、コメントが豊富で*4、情報量が多いのですが、載っていない重要なオプションもあるので、TypeScriptのコンパイラオプションも眺めるとよいでしょう。TypeScriptのアップデートに伴い tsc --init の出力もアップデートされてきているため、新しいプロジェクトを始めるときは tsc --init を確認するといいでしょう。

これでひとまず環境はで出来上がります。次のような hello.ts を用意して （これはJavaScriptとしても妥当なスクリプトです） 、

console.log ( "Hello, TypeScript!" );

ts-node で実行してみましょう。

$ npx ts-node hello.ts Hello, TypeScript!

簡単ですね。

とはいえ、ここにES modules/CommonJSなどで外部モジュールを読み込むと少し複雑になります。

例えば、次のようなNodeJSのfsモジュールを使うスクリプトを考えます。

import * as fs from "fs" ; console.log ( fs.readFileSync ( __filename ) .toString ());

このときimport文やrequire式は、TypeScriptとNodeJSそれぞれで独立して評価されます。つまり、TypeScript自身もモジュールを探して （module resolution） 、存在しなければコンパイルエラーにします。

ここでは、 @types/node という型定義モジュールが、fsモジュールの型定義やグローバル変数__filenameを提供します。試しに npm remove @types/node をしたあと npx tsc とコンパイルだけをしてみると、コンパイルエラーになはずです （ npm install -D @types/node で戻せるので気軽にどうぞ！） 。

「がんばらないTypeScript」の設定と実行

次に、「がんばらないTypeScript」を含めたTypeScriptのおすすめの設定を紹介します。先ほど生成したデフォルトの tsconfig.json を、次のように編集してください。

diff --git a/tsconfig.json b/tsconfig.json index ec795e8..02817b0 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,33 +3,33 @@ /* Basic Options */ "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ - // "lib": [], /* Specify library files to be included in the compilation. */ + "lib": ["es2019"], /* Specify library files to be included in the compilation. */ // "allowJs": true, /* Allow javascript files to be compiled. */ // "checkJs": true, /* Report errors in .js files. */ - // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ + "jsx": "react", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ // "declaration": true, /* Generates corresponding '.d.ts' file. */ // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ - // "sourceMap": true, /* Generates corresponding '.map' file. */ + "sourceMap": true, /* Generates corresponding '.map' file. */ // "outFile": "./", /* Concatenate and emit output to single file. */ - // "outDir": "./", /* Redirect output structure to the directory. */ + "outDir": "./build", /* Redirect output structure to the directory. */ // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ // "composite": true, /* Enable project compilation */ - // "incremental": true, /* Enable incremental compilation */ + "incremental": true, /* Enable incremental compilation */ // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ - // "removeComments": true, /* Do not emit comments to output. */ + "removeComments": false, /* Do not emit comments to output. */ // "noEmit": true, /* Do not emit outputs. */ // "importHelpers": true, /* Import emit helpers from 'tslib'. */ - // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ + "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ /* Strict Type-Checking Options */ "strict": true, /* Enable all strict type-checking options. */ - // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ - // "strictNullChecks": true, /* Enable strict null checks. */ - // "strictFunctionTypes": true, /* Enable strict checking of function types. */ - // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ - // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ - // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ + "noImplicitAny": false, /* Raise error on expressions and declarations with an implied 'any' type. */ + "strictNullChecks": false, /* Enable strict null checks. */ + "strictFunctionTypes": false, /* Enable strict checking of function types. */ + "strictBindCallApply": false, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ + "strictPropertyInitialization": false, /* Enable strict checking of property initialization in classes. */ + "noImplicitThis": false, /* Raise error on 'this' expressions with an implied 'any' type. */ // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ /* Additional Checks */ @@ -39,7 +39,8 @@ // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ /* Module Resolution Options */ - // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ + "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ + "forceConsistentCasingInFileNames": true, // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */

いずれも重要なのでひとつひとつ解説します。

がんばらないことと関係のないおすすめ設定

まず「がんばらないTypeScript」スタイルと関係のない設定から説明します。

+ "lib": ["es2019"]

これは、TypeScriptコンパイラに同梱されているライブラリでどれを使うかを指定します。ブラウザ用のコードであれば ["es2019", "dom", "dom.iterable"] 、NodeJS用のコードであれば ["es2019"] などにするのが定番でしょう。

+ "jsx": "react",

React/JSXを使うときは "react" にしてください。使わない場合はコメントアウトしたままでかまいません。なお、TypeScriptでJSX構文を使う場合は必ず拡張子を.tsxにする必要があります。

ちなみに筆者はviewライブラリのなかではReact推しです。これはReact自体が強力であることもさることながら、TypeScript処理系によるサポートが非常に強力だからです。

+ "sourceMap": true,

ソースマップの出力を有効にします。デバッガやスタックトレースでコンパイル前のTypeScriptのファイル名や行番号を表示するために必要です。

+ "outDir": "./build",

TypeScriptコンパイラは、デフォルトでソースファイルと同じディレクトリにコンパイル後のJavaScriptファイルを出力します。このデフォルト設定だとディレクトリが不必要に散らかるため、 build ディレクトリに出力するようにします*5。

+ "incremental": true,

差分ビルドを有効にしてコンパイルを高速化します。TypeScript v3.4で追加された新しいオプションで、まだ枯れているとはいえないものの、ビルドはかなり高速になります。

+ "removeComments": false,

コメントを残します。webpackを使う場合は、code splittingを制御するコメントを残す必要があるためです。

+ "downlevelIteration": true,

コンパイルターゲットがES5のとき、for-of構文を使うために必要です。

+ "moduleResolution": "node",

TypeScriptコンパイラが行うモジュールの検索をNodeJS互換にします。

+ "forceConsistentCasingInFileNames": true,

ファイル名の大文字小文字に一貫性がないときにコンパイルエラーにします。大文字小文字を区別しないファイルシステムを使っているときはこのオプションを有効にした方がよいでしょう。

がんばらないための設定の詳細

次に、「がんばらないTypeScript」スタイルの「柔らかい」設定です。

+ "noImplicitAny": false, + "strictNullChecks": false, + "strictFunctionTypes": false, + "strictBindCallApply": false, + "strictPropertyInitialization": false, + "noImplicitThis": false,

JavaScriptプロジェクトをTypeScriptに変換する場合、これらの設定はいったん無効にしておいて、変換作業を終えてから第二フェーズとして有効にすることをおすすめします。これらのオプションがすべて無効だとしても、JavaScriptよりはかなり「堅」く、型チェックやエディタによるサポートは受けられます。

TypeScriptを導入してしばらくして慣れてきたら、 noImplicitAny 以外のオプションを有効にするとより堅牢になります。 noImplicitAny は、新しくプロジェクトを始めるときには有効にするメリットがありますが、途中からTypeScriptに変換する場合には作業量に対して得るものが少ないのではないかと思っています。

筆者が関わっているKibelaも、途中でTypeScriptに変換したプロジェクトで、 noImplicitAny は無効のままです。有効にする予定もありません。それ以外のstrict系オプションは有効にしてありますが、それらはひとつひとつ様子を見ながら有効にしていきました。

JavaScriptプロジェクトをTypeScriptに変換する

JavaScriptプロジェクトにTypeScriptを導入するときは、次のようなプロセスで行います。

もともとES2015+で書いているJavaScriptプロジェクトで、コードが数千行から数万行程度のプロジェクトであれば、すべてのファイルを一括してTypeScript化した方がよいでしょう。

TypeScriptをインストールして tsconfig.json を用意する 拡張子を変更する コンパイルエラーをひたすら修正する

順に説明していきます。TypeScriptのインストールと設定は、すでに解説した通りです。ほとんどのケースで型定義モジュールはなくてかまいませんが、Reactを使う場合は @types/react と @types/react-dom を使った方がよいでしょう。

拡張子の変更では、すべての.js/.jsxファイルを.ts/.tsxにリネームします。renameコマンドを使うと簡単です。拡張子.jsでJSXを使っている場合は、無条件にすべて.tsxにしてしまう方がよいでしょう。

rename 's/\.js\z/.tsx/' src/**/*.js

コンパイルエラーの修正では、 npx tsc --watch でTypeScriptコンパイラのプロセスを常駐させます。いくつかのエラーは、次のように機械的に修正できます。

perl -i -pe 's/\bextends React\.(Pure)?Component /extends React.${1}Component<any, any> /' src/**/*.tsx

変換作業はとにかく素早く終えることが重要なので、いったん何も考えず any を宣言したり any にキャストしたりして、とにかくコンパイルを通しましょう。

コンパイルエラーが解決できたら、ほぼ終わったようなものです。

あとはts-loaderのドキュメントにあるように、webpackの設定に次のようなextensionとloader ruleを足します。

+ extensions: [".mjs", ".js", ".jsx", ".ts", ".tsx", ".json"],

module: { rules: [ + { + test: /\.(?:ts|tsx)$/, + exclude: /node_modules/, + use: { + loader: "ts-loader", + }, + }, ] }

これで動作確認ができるはずです。

最後に、テストファイルも同様にTypeScriptに変換して、動作確認をしたら終了です。テストの実行は、それぞれのテストフレームワークのTypeScript対応を参照してください。

また、すでに一度紹介済みですが、既存のJavaScriptプロジェクトをTypeScriptに変換したサンプルを用意したのでこちらも参照してみてください。

プログラミング言語としてのTypeScript

最後に、プログラミング言語としてのTypeScriptを少しだけ眺めてみます。

JavaScriptとの違い

JavaScriptからTypeScriptへの変換プロセスを見ると分かる通り、JavaScriptをTypeScriptに変換するにあたって必要なのは、ほとんど型宣言だけです。

型システムは少し学ぶ必要がありますが、「使う」だけならすぐ使えるはずです。

型コンテキスト

TypeScriptの型に関する構文を理解するにあたって重要な概念が「型コンテキスト」です。

型コンテキストは、TypeScriptの構文においてトークンや式が型として評価される場所を指します。これは公式に定義された用語ではありませんが、意識できると理解が簡単になるはずです。

例えば、 typeof expr 演算子（ type query ）は、通常は "string" などの文字列を返します。これに対して typeof expr を型コンテキストで使うと、 expr の型を返す型演算子となります。

例えば、次のように add() 関数を宣言します。

function add ( a: number , b: number ) : number { return a + b ; }

この add() 関数の型は (a: number, b: number) => number です。 typeof add 演算子の値を参照すると （つまり「値コンテキスト」で評価すると） "function" という文字列値を返します。まったく同じ typeof add という式を「型コンテキスト」で評価すると、その型である (a: number, b: number) => number が得られます。

const v = typeof add ; type f = typeof add ;

なお、TypeScriptの型を文字列化する標準的な方法がないので、 f の定義はエディタで見るしかありません。Typecript playgroundで f にカーソルを合わせるか、IntelliJ IDEAでcommandキー （macOSの場合） を押しながら f にカーソルを合わせると、これが確認できます。

型コンテキストはさらに、 null 、 undefined 、数値リテラル、文字列リテラル、真理値リテラル、そしてそれらのリテラルを要素とする配列リテラルとオブジェクトリテラルが使えます。型コンテキストとして評価する箇所は決まっているので、慣れると自然に読めるようになるとは思いますが、最初は少し引っかかるかもしれません。

構造的部分型

TypeScriptの型は、「同じインターフェイス （メソッドとプロパティ） があれば同じ型である」という構造的部分型という思想を採用しています。

例えば、ReadonlyArrayというArray型のサブタイプ （インターフェイスがある型のサブセットである型） は、TypeScriptで定義された、ただのinterface型です。

interface ReadonlyArray < T > { readonly length: number ; }

これでも、例えば次のような、ArrayからReadonlyArrayへの暗黙の型変換が可能なのです。

const a: RedonlyArray < string > = new Array < string >()

構造的部分型は、すべての型について適用されます。Arrayなどのビルトイン型や、HTMLElementなどのDOM APIも例外ではありません。

このルールは「すべての型は、型コンテキストではinterfaceとして扱われる」とも考えられます。これにより、例えばクラスをinterfaceとして実装することすら可能です。

class MyStringA extends String { } class MyStringB implements String { }

ジェネリクス

ところで Array<T> のように型パラメータを <...> で受けとる機能を、ジェネリクスといいます。Arrayの場合は、「 Array<string> （文字列の配列） 」と「 Array<number> （数値の配列） 」などを型として区別したいからです。

この型パラメータの中身は、型コンテキストで評価されます。

ジェネリクスは型をパラメータとして受けとり、新しい別の型を生成する「型関数」としての側面もあります。例えば、 Readonly<T> はTypeScript組み込みの型関数で、 T という型のプロパティすべてを readonly として宣言しなおした新しい型を返します。

type Point = { x: number ; y: number ; } ; type ReadonlyPoint = Readonly < Point >;

ジェネリクスや型関数による型演算は強力ですし、使いこなすと便利ではあります。しかし、これらはコンパイルの型チェックを強化するための機能で、コンパイルの後のコードの実行には何の影響もありません。これらとは、そのような意識で付き合うといいと思っています。

まとめ

TypeScriptについて、導入に際してハマりがちな設定と、TypeScript言語自体に対する考え方を紹介しました。

TypeScriptは奥が深いプログラミング言語ですが、とはいえ言語仕様自体はJavaScriptとほとんど同じです。それほど習熟していなくても、使い始めるのは簡単です。このままコンパイラとエコシステムが成長し続ければ、今後ますます重要な技術となっていくでしょう。

それでは、よいTypeScriptライフを！

関連記事