Slide 1

Slide 1 text

レガシーなアプリケーションにこそTypeScriptを採⽤するべきで はないかと思ったのでちょっとまとめてみたよっていう話をするス ライドです kansai.ts #3 → コロナ的な事情により開催中⽌ @miracle_fjsw 1

Slide 2

Slide 2 text

whoami const iam = { name: 'Takayuki Fujisawa', company: '株式会社ラクス', job: '品質向上や技術仕様の標準化などを組織横断的に推進するようなお仕事です', Twitter: '@miracle_fjsw' } 2

Slide 3

Slide 3 text

TypeScript Microsoft製のAltJS 静的型付き⾔語 JavaScript+型 TypeScriptコンパイラや、webpack、BabelなどでJavaScriptにトランスパイルして使⽤する フロントエンド(ブラウザ)、バックエンド(Node.js)双⽅の開発で活⽤できる 3

Slide 4

Slide 4 text

何を語るのか 保守性が⾼く、不具合が少ないアプリケーションを開発するという点において、型システムを持つ ⾔語を採⽤すると良いよね、という話は今更⾔うまでもない 新規に開発をする際は、どうぞTypeScriptを積極的に使っていただきたい ここでは、レガシーなコードで書かれたアプリケーションこそ、TypeScriptを導⼊するべきではない か、ということを4つの理由とともに語っていきたい 4

Slide 5

Slide 5 text

理由①『TypeScript is a typed superset of JavaScript』 5

Slide 6

Slide 6 text

理由①『TypeScript is a typed superset of JavaScript』 公式サイト https://www.typescriptlang.org/ の冒頭に出てくる⾔葉 TypeScriptはJavaScriptのスーパーセットである つまり、TypeScriptはJavaScriptの上位互換であり、JavaScriptの⽂法・知識がそのまま適⽤ できるよ、ということ TypeScript固有の⾔語仕様や、便利な使い⽅などは存在するが、それを習得していなくても TypeScriptを利⽤することができる 要は、JavaScriptと思って使うこともできるし、必要に応じてTypeScriptならではの機能を使うこ ともできる 6

Slide 7

Slide 7 text

理由② 型推論の恩恵 7

Slide 8

Slide 8 text

理由② 型推論の恩恵 型推論とは Wikipediaから抜粋 型推論(かたすいろん、英: type inference)とはプログラミング⾔語の機能の1つで、 静的な型付けを持つ⾔語において、変数や関数シグネチャの型を明⽰的に宣⾔しなくても、 初期化のための代⼊式の右辺値や関数呼び出し時の実引数などといった、 周辺情報および⽂脈などから⾃動的に(暗黙的に)各々の型を決定する機構のこと。 いちいち型を書くと冗⻑な記述になりがち、型システムによる安全性を享受しながら、冗⻑さを 回避したシンプルな記述ができる 型推論については賛否があり、型推論を活かしたコードは、明⽰的に型が表現されないので、 予想外の推論結果になったり、逆にわかりにくくなってしまうという意⾒もある 8

Slide 9

Slide 9 text

TypeScriptにおける型推論 コードの具体例をいくつか挙げながら、どのように型推論が⾏われ、コンパイル時の型チェックに利 ⽤されるかを列挙 9

Slide 10

Slide 10 text

let/const let foo = 'hello'; // 明⽰的に指定していないが、fooはstringであると認識される foo = 1; // コンパイルエラー console.log(foo); 1⾏⽬でfooは string であることがわかっているので、2⾏⽬の number 型の値を代⼊しようとす るとエラーになる 10

Slide 11

Slide 11 text

return値① function someFunc() { return 100; // 明⽰的に指定していないが、返却値は``number``であると認識される } let message: string = someFunc(); // コンパイルエラー someFuncの戻り値が number であることが分かっているので、 string 型の値への代⼊がコン パイルエラーになる 11

Slide 12

Slide 12 text

return値② function multi(a: number, b:number) { return a * b; //number * number = number } 引数が number なので、演算結果も number であると推論される 12

Slide 13

Slide 13 text

配列 let foo = [0, 1, 2] // foo: number[]と推論 let bar = [0, 1, 'aaa'] // bar: (number | string)と推論 13

Slide 14

Slide 14 text

Object const user = { name: 'Taro', // name: stringと推論 age: 20 // age: numberと推論 } 以下の型を持つオブジェクトであることが推論される const user: { name: string; age: number; } 14

Slide 15

Slide 15 text

JSON let data = [ {"id" : 100, "name" : "Taro" }, {"id" : 101,"name" : "Hanako" } ] 以下の型を持つオブジェクトであることが推論される let data: { id: number; name: string; }[] 15

Slide 16

Slide 16 text

TypeScriptの型推論がなぜレガシーコードにとって重要なのか? 前述の通り、TypeScriptの型推論の機能によって、型宣⾔を⾏っていなくても、型を推測し、コ ンパイル時に適切にチェックを⾏ってくれる これは、「もともと型の概念が存在していないコード」であったとしても、部分的に型安全を保って いくことができるということを意味している 16

Slide 17

Slide 17 text

型の概念がないコードが、TypeScriptの導⼊によって恩恵を得るサンプル 下記のようなコードがすでにシステム上に実装されているコードだとする function someFunc(foo) { if (foo > 0) { return foo * 2; } } var bar = someFunc(1); console.log(bar.toString()); もちろん、このJavaScriptは問題なく動作する。しかし、このコードには⼀つの問題が潜んでいる。 パラメータに負値を渡すとどうなるだろうか? 17

Slide 18

Slide 18 text

var baz = someFunc(-1); // 負の値を渡すとundefinedが帰ってくる console.log(baz.toString()); baz.toString() は、bazがundefinedなので、ランタイムエラーが発⽣する これは実⾏しなければ検出できない では、これをTypeScript環境でコンパイルするとどうなるか? 18

Slide 19

Slide 19 text

コンパイルエラーが発⽣する var baz = someFunc(-1); console.log(baz.toString()); // コンパイルエラー:Object is possibly 'undefined'. コンパイラがコードを解析して型推論した結果は次の通りとなり、 number または undefined を 返すメソッドであると認識された function someFunc(foo: any): number | undefined someFunc は undefined を返す可能性があるため、その返却値を代⼊している baz に対する 操作が危険であることを、コンパイラが教えてくれる 19

Slide 20

Slide 20 text

someFuncを安全に使⽤するには、次のようなコードに修正する必要がある var bar = someFunc(-1); if (bar === undefined) { // undefinedのチェック bar = 0; } console.log(bar.toString()); 上記のように記述すれば、コンパイルエラーは起きなくなる。 bar === undefined のチェックがあることで、 toString が実⾏されるタイミングでは number し か到達しないことをTypeScriptが理解するようになる このような型の絞り込み処理を「型ガード」と呼ぶ 20

Slide 21

Slide 21 text

先程は someFunc を使う側のコードの問題をTypeScriptが検出してくれる例を⾒たが、実は someFunc ⾃体にも問題が潜んでいる コンパイル時のオプション noImplicitReturns を有効にすると、次のようなコンパイルエラーが発 ⽣する function someFunc(foo) { // コンパイルエラー:Not all code paths return a value. if (foo > 0) { return foo * 2; } } someFunc がすべてのパスで返却値を返しておらず、意図しないundefinedを返す可能性を教 えてくれている 21

Slide 22

Slide 22 text

このように、既存コードに型宣⾔など、型の情報を与えていないにもかかわらず、型推論によって 未然に不具合を検出することができている 22

Slide 23

Slide 23 text

理由③ JavaScriptライブラリとの連携 23

Slide 24

Slide 24 text

理由③ JavaScriptライブラリとの連携 レガシーなシステムにはすでにたくさんのJavaScriptで記述されたライブラリが使⽤されている TypeScriptには、こうしたJavaScriptライブラリとうまく連携する仕組みが備わっている JavaScriptで記述されたライブラリをTypeScriptに導⼊する⽅法はいくつか存在する 型宣⾔ファイルを⾃作する DefinitelyTypedで公開されている型定義ファイルを使⽤する allowJSオプションを活⽤する 24

Slide 25

Slide 25 text

型宣⾔ファイル(d.tsファイル)を⾃作する 「型宣⾔ファイル」を作成し、TypeScriptにライブラリの型情報を伝えることで実現する⽅法 例えば、次のようなJavaScriptで記述されたライブラリ calc.js があるとする // calc.js exports.sub = function(a, b) { return a - b; } 25

Slide 26

Slide 26 text

このライブラリを利⽤するために、次のような型宣⾔ファイルを作成して配置する。(今回の例で は、tsファイルと同じディレクトリに配置した) // calc.d.ts export function sub(a: number, b: number): number; これにより、 exec.ts は calc.d.ts を通して sub 関数を認識することができ、コンパイルが通る ようになる // exec.ts import { sub } from "./calc"; console.log(sub(100, 1)); 型宣⾔ファイルの定義に基づき、コンパイル時に型推論や型チェックが⾏われるようになる もちろん、IDEにも認識されるのでコード補完なども有効になる 26

Slide 27

Slide 27 text

DefinitelyTyped(@types)で公開されている型定義ファイルを使⽤する jQueryやChart.jsといったライブラリをTypeScriptに導⼊する際にも、この「型宣⾔ファイル」が必 要になる これを⾃分で作成するのはとても⼤変である DefinitelyTyped(http://definitelytyped.org/)というコミュニティプロジェクトが存在しており、こ こにはJavaScriptで記述されたライブラリの型宣⾔ファイルが集まっている あくまでコミュニティによるOSSなので、最新バージョンに追従していない、正しくないという場 合も無くはないので注意 DefinitelyTypedに対象のライブラリの型宣⾔ファイルが存在するかは、 TypeSearch(https://microsoft.github.io/TypeSearch/) で検索することができる 27

Slide 28

Slide 28 text

DefinitelyTypedに登録されている型宣⾔ファイルは、npmコマンドでプロジェクトに取り込むこと ができる npm install --save @types/jquery 28

Slide 29

Slide 29 text

jQueryの型定義が導⼊された状態で、jQueryの addClass メソッドを記述してみる。 TypeScriptがjQueryのメソッドを認識しているので、コンパイルエラーにはならない。 $(function() { $('#foo').addClass('bar'); }); ここで、 addClass の引数に数値を渡してみる。 $(function() { $('#foo').addClass(1); }); 下記のようなコンパイルエラーが発⽣する。jQueryが提供する関数の型定義が正しく認識されて いることがわかる Argument of type '1' is not assignable to parameter of type 'string | string[] | ((this: HTMLElement, index: number, currentClassName: string) => string)'. 29

Slide 30

Slide 30 text

allowJSオプションを使⽤する JavaScriptで記述されたライブラリをTypeScriptに導⼊するには、 allowJS オプションを有効に する⽅法もある(通常は.ts拡張⼦のみコンパイルできる) 先出の calc.js に対して、 allowJS オプションを有効にすると、TypeScriptと⼀緒にコンパイル 対象になる $ tsc --allowJS calc.js exec.ts 30

Slide 31

Slide 31 text

ただし、素の状態のJavaScriptは型の情報が与えられていないため、かなり緩い型推論結果に なる(引数は any 、戻り値は number ) // calc.js exports.sub = function(a, b) { return a - b; } 推論されたシグネチャ sub(a: any, b: any): number とはいえ、もともと型の概念を持っていなかったレガシーなライブラリが、ただ読み込ませただけで、 多少なりとも型の能⼒を⼿に⼊れることができている 31

Slide 32

Slide 32 text

JSDocによって型定義をすることも可能 /** * @param {number} a * @param {number} b * @return number */ exports.sub = function(a, b) { return a - b; } 上記のようにJSDocを記述すると、 sub メソッドを次の通り解釈させることができる sub(a: number, b: number): number 32

Slide 33

Slide 33 text

型推論の恩恵を得るだけでも⼗分であることはすでに述べたが、既存プロジェクトがきっちりと JSDocが記述している環境であれば、TypeScriptへの移⾏はなおさら恩恵を得やすいと⾔える 以下は、状況に応じて使い分けるとよい 型宣⾔ファイルの⾃作 DefinitelyTypedの使⽤ allowJS・JSDocの使⽤ なお、ライブラリ⾃⾝が型宣⾔ファイルを公開している場合もある まずはライブラリ公式のもの、もしくはDefinitelyTyped上のものを探し、存在していない場合は allowJSオプションを使⽤して、徐々に型宣⾔ファイルを作る、もしくはライブラリ側をTypeScript 仕様に変える、というアプローチが良いのではないだろうか 33

Slide 34

Slide 34 text

理由④ 段階的なTypeScript化を助けるコンパイルオプション 34

Slide 35

Slide 35 text

理由④ 段階的なTypeScript化を助けるコンパイルオプション TypeScriptには、コンパイル時のチェック内容を変えることができるオプションが備わっている 35

Slide 36

Slide 36 text

noImplicitAny 暗黙的に型推論の結果 any 型となった場合にコンパイルエラーにするオプション OFFにすることで、暗黙的な any を許容できる 明⽰的に型指定を⾏わない場合、メソッドの引数などは any 型になる any 型は、その名前の通り「なんでもあり」の型なので、あまり良いものではなく、 any 型を避け るのが望ましい ただし、もともとJavaScriptで記述されているコードについては、⼀旦このオプションをOFFにしてお いて、コンパイルエラーを⽬印に、徐々に型指定を増やしていくのが良いと思われる 36

Slide 37

Slide 37 text

strictNullChecks null型、undefined型にのみ、 null 、 undefined の代⼊を許可する設定 null型: null のみを許容する特殊な型 undefined型: undefined のみを許容する特殊な型 T | null 型と null 型は別物と扱われ、T | null 型には null を代⼊できない これにより、意図しない undefined や、意図しない null を防ぐことができる noImplicitAny同様、最初はオプションをOFFにしておき、ONにした時に⾒つかるコンパイルエラ ーを徐々に修正すると良いと思われる 37

Slide 38

Slide 38 text

ずさんなコードの匂いを検出してくれる便利なコンパイルオプション noUnusedLocals 未使⽤のローカル変数があるとエラーになる noUnusedParameters 未使⽤の引数があるとエラーになる noImplicitReturns 既出。メソッド内の全てのパスでreturn⽂が無い場合エラーになる noFallthroughCasesInSwitch Switch⽂内におけるbreak漏れがエラーになる 38

Slide 39

Slide 39 text

結論:TypeScriptは、JavaScriptプロジェクトからうまく移⾏する環境が整っている TypeScriptはJavaScriptのスーパーセットなので、既存のコードをそのまま使うことができる 型推論によって、ただのJavaScriptが型の⼒を⼿に⼊れることができる JavaScriptで記述された既存資産を柔軟に組み⼊れる仕組みやそれを⽀えるコミュニティが存 在する 柔軟なコンパイルオプションによって、レガシーなコードを段階的に厳密なコードに変えていくことが できる 39

Slide 40

Slide 40 text

型の無い世界に、終⽌符を。 40

Slide 41

Slide 41 text

以下、Appendix ※別の切り⼝でまとめようかなと模索していたときの残骸集 41

Slide 42

Slide 42 text

明⽰的に型を書くとき、書かないとき 型推論の是⾮についてはここでは議論しない。型推論を積極的に使っていく場合に関して、個 ⼈的に「なるほど」と思った記事 http://ducin.it/typescript-type-inference-guide @tomasz_ducin 上記の記事において、型を明⽰かするのは次の箇所であると述べている 外部とのI/Fになる部分 データ構造 メソッドの引数・返却値 特に上2つについては納得感が強い。多くの場合、システムに機能が追加されるとき、外部との I/Fやデータ構造の変更が⾏われるので、この2点に関して型を明⽰化しておくだけで、型のメリ ットを得られそうである。 42

Slide 43

Slide 43 text

どの本読んだらいいですか わかんないですけど、私が読んだ本の順番とコメント置いておきます 速習TypeScript (https://www.amazon.co.jp/dp/B0733113NK) まずTypeScriptを「完全に理解した」状態にするのにおすすめ 実践TypeScript (https://www.amazon.co.jp/dp/483996937X) 型やTypeScriptの便利な機能について詳しく書かれているので、「完全に理解した」状態 から「⾊々わかってないことに気づくフェーズ」にちょうどよい 後半にReactやVue、Node.jsに導⼊する際の実践例の記載もあり、満⾜度⾼め TypeScript実践プログラミング (https://www.amazon.co.jp/dp/4798139807) ⼀つ⼀つは深くないが、上記2冊よりは広い分野の話が書かれていて知識の補完によか った 43

Slide 44

Slide 44 text

TypeScript始めようと思ったらNode.js⼊れろって⾔われました。別にサーバ ーサイド興味ないですが。 正確には npm が必要。ただ、ちょっと動かしてみたいときにNode⼊ってると便利なので結局 Node⼊れることになると思う。 44

Slide 45

Slide 45 text

便利なPlayground Playground http://www.typescriptlang.org/play/ ブラウザ上で動作するTypeScriptの実⾏環境 ちょっと書いて動作確認したいときに便利 45

Slide 46

Slide 46 text

トランスパイルするESのバージョンは指定できる 元コード(ES6以降で導⼊されたアロー関数を使っている) var str = (arg1, arg2) => { console.log('hoge'); } ES5を指定 var str = function (arg1, arg2) { console.log('hoge'); }; ES6を指定 var str = (arg1, arg2) => { console.log('hoge'); }; 46

Slide 47

Slide 47 text

以上 47