Upgrade to Pro — share decks privately, control downloads, hide ads and more …

レガシーなアプリケーションにこそTypeScriptを採用するべきではないかと思ったのでちょっ...

レガシーなアプリケーションにこそTypeScriptを採用するべきではないかと思ったのでちょっとまとめてみたよっていう話をするスライドです / Legacy code needs TypeScript

Takayuki Fujisawa

March 19, 2020
Tweet

More Decks by Takayuki Fujisawa

Other Decks in Technology

Transcript

  1. whoami const iam = { name: 'Takayuki Fujisawa', company: '株式会社ラクス',

    job: '品質向上や技術仕様の標準化などを組織横断的に推進するようなお仕事です', Twitter: '@miracle_fjsw' } 2
  2. 理由①『TypeScript is a typed superset of JavaScript』 公式サイト https://www.typescriptlang.org/ の冒頭に出てくる⾔葉

    TypeScriptはJavaScriptのスーパーセットである つまり、TypeScriptはJavaScriptの上位互換であり、JavaScriptの⽂法・知識がそのまま適⽤ できるよ、ということ TypeScript固有の⾔語仕様や、便利な使い⽅などは存在するが、それを習得していなくても TypeScriptを利⽤することができる 要は、JavaScriptと思って使うこともできるし、必要に応じてTypeScriptならではの機能を使うこ ともできる 6
  3. 理由② 型推論の恩恵 型推論とは Wikipediaから抜粋 型推論(かたすいろん、英: type inference)とはプログラミング⾔語の機能の1つで、 静的な型付けを持つ⾔語において、変数や関数シグネチャの型を明⽰的に宣⾔しなくても、 初期化のための代⼊式の右辺値や関数呼び出し時の実引数などといった、 周辺情報および⽂脈などから⾃動的に(暗黙的に)各々の型を決定する機構のこと。

    いちいち型を書くと冗⻑な記述になりがち、型システムによる安全性を享受しながら、冗⻑さを 回避したシンプルな記述ができる 型推論については賛否があり、型推論を活かしたコードは、明⽰的に型が表現されないので、 予想外の推論結果になったり、逆にわかりにくくなってしまうという意⾒もある 8
  4. let/const let foo = 'hello'; // 明⽰的に指定していないが、fooはstringであると認識される foo = 1;

    // コンパイルエラー console.log(foo); 1⾏⽬でfooは string であることがわかっているので、2⾏⽬の number 型の値を代⼊しようとす るとエラーになる 10
  5. return値① function someFunc() { return 100; // 明⽰的に指定していないが、返却値は``number``であると認識される } let

    message: string = someFunc(); // コンパイルエラー someFuncの戻り値が number であることが分かっているので、 string 型の値への代⼊がコン パイルエラーになる 11
  6. return値② function multi(a: number, b:number) { return a * b;

    //number * number = number } 引数が number なので、演算結果も number であると推論される 12
  7. 配列 let foo = [0, 1, 2] // foo: number[]と推論

    let bar = [0, 1, 'aaa'] // bar: (number | string)と推論 13
  8. Object const user = { name: 'Taro', // name: stringと推論

    age: 20 // age: numberと推論 } 以下の型を持つオブジェクトであることが推論される const user: { name: string; age: number; } 14
  9. JSON let data = [ {"id" : 100, "name" :

    "Taro" }, {"id" : 101,"name" : "Hanako" } ] 以下の型を持つオブジェクトであることが推論される let data: { id: number; name: string; }[] 15
  10. 型の概念がないコードが、TypeScriptの導⼊によって恩恵を得るサンプル 下記のようなコードがすでにシステム上に実装されているコードだとする function someFunc(foo) { if (foo > 0) {

    return foo * 2; } } var bar = someFunc(1); console.log(bar.toString()); もちろん、このJavaScriptは問題なく動作する。しかし、このコードには⼀つの問題が潜んでいる。 パラメータに負値を渡すとどうなるだろうか? 17
  11. コンパイルエラーが発⽣する var baz = someFunc(-1); console.log(baz.toString()); // コンパイルエラー:Object is possibly

    'undefined'. コンパイラがコードを解析して型推論した結果は次の通りとなり、 number または undefined を 返すメソッドであると認識された function someFunc(foo: any): number | undefined someFunc は undefined を返す可能性があるため、その返却値を代⼊している baz に対する 操作が危険であることを、コンパイラが教えてくれる 19
  12. someFuncを安全に使⽤するには、次のようなコードに修正する必要がある var bar = someFunc(-1); if (bar === undefined) {

    // undefinedのチェック bar = 0; } console.log(bar.toString()); 上記のように記述すれば、コンパイルエラーは起きなくなる。 bar === undefined のチェックがあることで、 toString が実⾏されるタイミングでは number し か到達しないことをTypeScriptが理解するようになる このような型の絞り込み処理を「型ガード」と呼ぶ 20
  13. 先程は someFunc を使う側のコードの問題をTypeScriptが検出してくれる例を⾒たが、実は someFunc ⾃体にも問題が潜んでいる コンパイル時のオプション noImplicitReturns を有効にすると、次のようなコンパイルエラーが発 ⽣する function

    someFunc(foo) { // コンパイルエラー:Not all code paths return a value. if (foo > 0) { return foo * 2; } } someFunc がすべてのパスで返却値を返しておらず、意図しないundefinedを返す可能性を教 えてくれている 21
  14. このライブラリを利⽤するために、次のような型宣⾔ファイルを作成して配置する。(今回の例で は、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
  15. 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
  16. ただし、素の状態のJavaScriptは型の情報が与えられていないため、かなり緩い型推論結果に なる(引数は any 、戻り値は number ) // calc.js exports.sub =

    function(a, b) { return a - b; } 推論されたシグネチャ sub(a: any, b: any): number とはいえ、もともと型の概念を持っていなかったレガシーなライブラリが、ただ読み込ませただけで、 多少なりとも型の能⼒を⼿に⼊れることができている 31
  17. 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
  18. noImplicitAny 暗黙的に型推論の結果 any 型となった場合にコンパイルエラーにするオプション OFFにすることで、暗黙的な any を許容できる 明⽰的に型指定を⾏わない場合、メソッドの引数などは any 型になる

    any 型は、その名前の通り「なんでもあり」の型なので、あまり良いものではなく、 any 型を避け るのが望ましい ただし、もともとJavaScriptで記述されているコードについては、⼀旦このオプションをOFFにしてお いて、コンパイルエラーを⽬印に、徐々に型指定を増やしていくのが良いと思われる 36
  19. strictNullChecks null型、undefined型にのみ、 null 、 undefined の代⼊を許可する設定 null型: null のみを許容する特殊な型 undefined型:

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