2016年6月16日
私がTypeScriptについて勘違いしていたこと、そしてその理由
(2016-06-05)by Tatu Tamminen
本記事は、原著者の許諾のもとに翻訳・掲載しております。
(訳注:2016/9/28、頂きましたフィードバックを元に記事を修正いたしました。)
何か新しいものが登場したと聞くと、人はそれに対する賛否を選ぶ傾向があります。TypeScriptが登場したときの私は、キーコンセプトのうち自分に合わないものをほんの幾つか取り上げて「否」の側を選んでしまいました。この記事では、私が当時どのように考えていたか、そして私がどのようにして「TypeScriptの背景には、大きな犠牲なしに利点を得る方法を知る人々による偉大な考えがあった」ことに気付いたかを説明しようと思います。
TypeScriptが登場したときの私の考え
Anders Hejlsbergが何かに取り組んでいるときは、つい私はそちらに注意を完全に傾けてしまいます。彼はコンパイラの構築やプログラミング言語の設計を30年近く経験してきています。彼の様々なプログラミング言語に関する貢献については 彼のWikipediaのページ でもっと読むことができます。
「彼がJavaScriptにトランスパイル(つまり、ソースコードからソースコードへのコンパイル)される言語に取り組んでいる」と聞いた時、私の第一印象は失望でした。MicrosoftはDart/CoffeeScriptの道をたどり、ECMAScriptの標準の道を拒んだのだと。私は何かを短く書くためだけに、新しい言語を学んでこれまで学んだことを忘れてしまうようなことはしません。もっとメリットがないと、そうする理由がありません。
また、Microsoftの「ASP.NET Webフォームを導入することでWindowsの開発者をWeb開発に取り込もうとした」というやり方について、私には悪い思い出もありました。Webフォームは、Webの中核となる技術を、混在した結果とともに抽象化してしまいました。「TypeScriptは、JavaScriptを”学べない”C#開発者を対象にしたものなのではないか?」「新しい言語は、C#の開発者が使い慣れた機能を持つものになるのではないか?」
Hejlsbergがチームにいてさえも、私はTypeScriptに単純に歓喜することはできず、それ以上深く探ることはしませんでした。その言語とコンパイラに関するいくつかのキーポイントを完全に見落としていたのです。
TypeScriptのメリットを今すぐ得る
最近のクライアントのプロジェクトで、TypeScriptが React と Redux と共に使われていました。ReactとReduxは共に私にはなじみがあるもので、モダンなJavaScript (ECMAScript 2016, ES6) も6か月ほど書いていました。
TypeScriptを始める方法の一つは、お気に入りのエディタを一つ選び(例えば Visual Studio Code 、 Sublime Text 、あるいは Atom )、そこからTypeScriptのコンパイラを実行することです。私は TypeScript Sublime Plugin をインストールし、ネイティブHTML要素での幾つかの自動補完サジェストを試しました。
技術的な注記:テキストエディタのプラグインは、TypeScriptコンパイラのAPIの一部であるLanguage Serviceを利用します。これは、開発者にとって低レイテンシでのフィードバックが重要である場合のテキスト編集のために設計されています。上記のアニメーションの例の getCompletionsAtPosition など、美しいAPIを提供しています。
TypeScriptとサードパーティのライブラリ
ここまでまだTypeScriptをまったく書いておらず、.ts拡張子を持つES5のソースコードファイルを書いただけです。サードパーティライブラリのAPI記述を入手することで、コンパイラからもっと多くの利点を得られます。API記述をTypeScriptではType Definition(型定義)と呼びます。
有名なViewライブラリであるReactと、その型定義をダウンロードしてみましょう。型定義ファイルは、react.d.tsのように、ファイル名の”d”から判別できます。
型定義を管理するツールは Typings といいます。
Typingsをインストールするには、 npm install typings -g
を実行します。
TypingのコマンドラインAPIが馴染みのあるものであることに気付くのではないでしょうか。npmで用いられるものによく似ているのです。
Typingsをインストールした後は、 init コマンドを用いて設定ファイルを生成します。 : typings init
search コマンドを–nameフラグと共に使い、型定義を名前で検索しましょう。
> typings search --name react
Viewing 1 of 1
NAME SOURCE HOMEPAGE DESCRIPTION VERSIONS UPDATED
react dt http://facebook.github.io/react/ 2 2016-05-26T13:46:01.000Z
“dt”は型定義のソースの場所を表します。これは例えば、npmであったり、Definitely Typed(dt)という有名なサイトだったりします。ソースのリストは ここ で見られます。
型定義は install コマンドでインストールできます。
typings install dt~react dt~react-dom --save
–save フラグはnpmの –save フラグと似た挙動を持ちます。つまり、型定義の参照を、 typings init
で生成されたtyping.jsonに保存します。
まだReactそのものをインストールしていませんでしたが、npmでインストールできます。
npm install react react-dom --save
サードパーティのライブラリとその型定義ファイルがある状態での開発とは、一体どのようなものになるのでしょう?私は例として、2つのプロパティを持つReactコンポーネントを作り、それを使ったり編集したりする際に自動補完がどのように動作するかを観察しました。
この例では、TypeScriptの最初のスニペットとしてインターフェース宣言を追加しています。私はあらゆる場面で型を追加することに厳格ではありませんが、外部に露出するエントリーポイント(コンポーネントのプロパティなど)を説明することは有益です。
型定義のメリット
これで、APIの定義を覚えたり覗いたりする必要がなくなり、APIを利用するのが簡単になりました。リファクタリングももっと簡単になります。
ここで、他のたくさんのコンポーネント内で使われていて、異なるプロパティが必要な DemoComponent があると仮定しましょう。例えば、 name プロパティがあり、より特化した他のプロパティ(username, surname, fullNameなど)に変更されうるとします。大きなプロジェクト内で’.name’というキーワードで検索・置換を行うと、端的に言って、あなたは参ってしまうでしょう。正しいコンテキストかどうかについて、検索でヒットしたものをすべてチェックしなければなりません。型が適切な箇所で用いられていれば、 name プロパティはより語義に忠実な意味になり、さらに、ツール側にもリファクタリングで影響される名前がどれであるかわかるようになります。
上記のアニメーションで示した例はシンプルですが、大きなコードベースでも同じような操作ができることは容易に想像できるでしょう。
型に関する誤解
“強い型付け”と聞くと、「古い(型インターフェースなど以前の)C#やJavaのコードのように、あらゆる箇所で型が必要になる」と考えられがちです。
IDictionary<int, Car> carsById = new Dictionary<int, Car>();
これでは、利点がないのに多くのノイズがあります。
TypeScriptには型推論という機能があり、以下のように入力するとエラーを吐くように動作します
let name = "tatu"
name = 2; // an error
nameが String であることが推論されているため、明示的に示す必要がないのです。
また、TypeScriptにはContextual Type(文脈的型付け)という機能があります。以下は ドキュメント からの例です。
window.onmousedown = function(mouseEvent) {
console.log(mouseEvent.buton); //<- Error
};
mouseEvent には定義された型はありませんが、それでもTypeScriptは mouseEvent が button というプロパティを持たない場合にエラーを投げることができます。どうしてこれが可能なのでしょう?
それは、TypeScpritが「 onmousedown に対するコールバックは MouseEvent 型の引数をとる関数である」と知っているからです。 MouseEvent 型は button プロパティを持ちません。こういったチェックにより、開発者の時間を節約することができます。
TypeScriptを他のツールと共に使う
JavaScriptの開発で使ってきた他のツールについて考えたとき、思い至ったのは以下のものでした:リンティングツール、Browserify/Webpack、テストフレームワーク、そしてBabelJSです。これらのツールは、TypeScriptによって時代遅れになるでしょうか?
lintツール
エラーの捕捉においてTypeScriptがとても強力であれば、果たしてESLintやJSHintなどのツールは必要でしょうか?答えは「Yes」です。例えば、エラーのチェックに加え、ESLintはベストプラクティスや文体といった面でのコードフォーマットのチェックを行えます。残念ながらESLintとの互換性は TypeScript ESLint Parser プロジェクトに依存しており、このプロジェクトはまだ実験段階です。
良いニュースとしては、TypeScriptのために作られた TSLint というツールが存在します。
Browserify/Webpack
TypeScriptはバンドルツールではないため、例えば「自分自身のコードはTypeScriptで書いているが、ブラウザではサードパーティ製ライブラリであるlodashを使っている」というケースで、TypeScriptではlodashをフェッチして1つのファイルにバンドルすることができません。バンドルするのはBrowserify/Webpackの役割になります。
テストフレームワーク
私はTypeScriptでテストを書き、JavaScriptにコンパイルし、テストの実行などのためにMochaを使う、ということをやりました。これに関しての皆さんの意見を聞きたいです。
BabelJS
TypeScriptがES2016の機能のサポートしているということは、BabelJSはもう不要でしょうか?答えは「状況による」です。賢い人に、BabelJSのもたらす利点について尋ねてみました。
@_Tx3 TypeScript can’t currently compile async/await to ES5. If you need that, you need Babel or something else.
— Tero Parviainen (@teropa) 2016年5月20日
著者:「ES2015のコードをトランスパイルするのに、#TypeScript コンパイラではなく#babeljs を使わなければいけない理由を、誰か教えてくれませんか?」
Tero Parviainen:「TypeScriptは今のところasync/awaitをES5にコンパイルできません。もしこれらが必要であればBabel等を使う必要があります」
言い換えれば、TypeScriptはasync/awaitをサポートしているが、それを全ブラウザで理解可能なECMAScriptにコンパイルすることはできません。
この機能を使わないのであれば、ビルドプロセスからBabelJSを消してしまっても構わないのです。
追記:読者のBirk Skyumが指摘してくれたのですが、この機能のES3へのサポートは TypeScriptのロードマップ に存在しており、バージョン2.0でリリースされる予定とのことです。
TypeScriptを使うべき時とは
TypeScriptに関するブログ投稿のほとんどは、「TypeScriptは大きなプロジェクトでの使用を企図している」と言及しています。Anders Hejlsbergの JavaScript Jabberでのインタビュー(30:50) によると、
もし5行だけのコードを書くのであれば、それはTypeScriptの導入という努力には値しないでしょう…… プロジェクトが大きくなればなるほど、TypeScriptの有用性は上がっていきます。さきに私が述べたように、数千行のコードになるまでには、スラム・ダンク(大成功)になっているでしょう。
* 「スラム・ダンク」がわからず、Google検索しなければなりませんでした。どうやら、外すほうが難しいような簡単なシュートを指すバスケット用語のようです。
結論
願わくば、少なくとも私のTypeScriptへの熱狂が皆さんに伝わり、それで皆さんがTypeScriptにもう一度チャンスを与えるようになれば幸いです。実際のプロジェクトでTypeScriptを使って成功したか、あるいは惨めな経験をしたか、是非お聞きしたいです。それではまた今度!
株式会社リクルート プロダクト統括本部 プロダクト開発統括室 グループマネジャー 株式会社ニジボックス デベロップメント室 室長 Node.js 日本ユーザーグループ代表
- Twitter: @yosuke_furukawa
- Github: yosuke-furukawa