Slide 1

Slide 1 text

TypeScript入門 雫石 卓耶(@sititou70)

Slide 2

Slide 2 text

自己紹介 ● 名前:雫石 卓耶(しずくいし たくや)(@sititou70) ● 2021年に新卒でリクルートに入社しました ● 所属:横断エンジニアリング部 アプリケーション・ソリューショ ン・グループ(ASG) ● 普段:TypeScript、React、Next.jsでWebアプリケーションを書 いています ● 趣味:型パズルなど ● よろしくお願いします 2 フローリングの アイコンが私

Slide 3

Slide 3 text

研修で学ぶこと ● TS(TypeScript)とは何か(座学9、演習1) ● TSの型、コードの書き方(座学5、演習5) ● その他、周辺知識として知っておいたほうが良いこと(ほぼ座学のみ) 3

Slide 4

Slide 4 text

研修のゴール ● 型と友だちになること ○ 型エラーは仕事を増やす敵ではなく ○ コードの不具合(将来の仕事)を減らしてくれる仲間であることを実感しましょう 4

Slide 5

Slide 5 text

「それは既に知っているよ」という人 ● スライドの最後にある型パズルでもやって遊んでいてください ○ 演習もやらなくて大丈夫です ■ Slackの進捗確認には「パス」でリアクションください ● または、任意のタイミングで研修に戻っても大丈夫です ● もしパズルが解けたら、せっかくなので研修の最後にでも共有してほしいです 5

Slide 6

Slide 6 text

研修で学ぶこと ● TS(TypeScript)とは何か ● TSの型、コードの書き方 ● その他、周辺知識として知っておいたほうが良いこと 6

Slide 7

Slide 7 text

TS(TypeScript)とは何か ● Microsoft社が開発しているプログラミング言語 ● Alt JS:JavaScriptにコンパイルされる ● 「(静的な)型が付いたJSのスーパーセット」とよく言われる 7

Slide 8

Slide 8 text

Alt JS:JavaScriptにコンパイルされる(1/3) ● TSのコードはJSにコンパイルされます ○ 「トランスパイル」とも言われます ● 実行はJSエンジンが行います 8

Slide 9

Slide 9 text

Alt JS:JavaScriptにコンパイルされる(2/3) ● いろいろあるAlt JS ○ Flow, CoffeeScript, Haxe, PureScript, Reason… ● ブラウザで動かせる言語は基本的にJSだけだから ● 一時期はどのAlt JSが天下を取るかという状況だったが、最近はTSに軍配が上がりつ つある様子 9 Alt JSのリストの参考:https://github.com/jashkenas/coffeescript/wiki/List-of-languages-that-compile-to-JS#javascript-extensions

Slide 10

Slide 10 text

Alt JS:JavaScriptにコンパイルされる(3/3) ● 古いブラウザでも動くようにコンパイルすることもできます ● Downlevelingといいます ○ 例:async / awaitはそのままではIEでは動作しないが、TSが頑張ってコードを変換 してくれる ● もちろん、「まったくDownlevelingしない」ということもできます ○ 例:tsは型検査だけで、コンパイルはBabelなど別のツールに任せる 10

Slide 11

Slide 11 text

TSは 「(静的な)型が付いたJSのスーパーセット」 11

Slide 12

Slide 12 text

JSのスーパーセットとは ● すべてのJSのコードは、文法的に有効なTSのコードでもあります ○ 例:以下のようなJSのコードがあります ○ これをTSのPlaygroundにそのままコピペして動かせます ● JSから地続きで学習できるというメリット ● (JSの良くない部分を引き継ぐ場合もあるというデメリット) 12 const msgString = "hello world"; console.log("現在のメッセージは" + msgString.length + "文字です");

Slide 13

Slide 13 text

ミニ演習1 1. TS Playgroundを開きます 2. TS Config -> Type Checking -> strictが有効になっていることを確認します 3. 上記のコードを実際にTS Playgroundへコピペしてみてください ○ 実行できますか ○ コンパイルの結果、どのようなJSになりますか ■ 右側の.JSタブから確認します 13 const msgString = "hello world"; console.log("現在のメッセージは" + msgString.length + "文字です");

Slide 14

Slide 14 text

TSは 「(静的な)型が付いたJSのスーパーセット」 14

Slide 15

Slide 15 text

JSは動的な検査を行う言語(1/2) ● 実行するまでエラーがわからないのが辛い ● 1行目を次のように書き間違えたとします ● 実行するとエラーになります ● 2行目の「msgString.length」を実行しようとしてエラーになりました 15 const msgString = null; console.log("現在のメッセージは" + msgString.length + "文字です"); TypeError: Cannot read properties of null (reading 'length')

Slide 16

Slide 16 text

JSは動的な検査を行う言語(2/2) ● または、次のように間違えたとします ● こちらは実行してもエラーが出ません ● そういう仕様なので 16 const msgString = 12345; console.log("現在のメッセージは" + msgString.length + "文字です"); 現在のメッセージはundefined文字です

Slide 17

Slide 17 text

開発・テスト時に気づけるか……? ● 気づけないとユーザーのブラウザ上で実行時エラーになります ○ やばい 17

Slide 18

Slide 18 text

いやいや ● 「そもそもmsgStringは文字列が入る変数なんだから、nullや数値を代入している1行目 の時点でおかしいでしょ」 ○ ……と、思いましたよね? ○ msgStringという変数名から「この変数にはなんらかの文字列(文字列”型”の値)を 入れるぞ」という作者の気持ちが伝わってきたからです ● TSの静的型検査器も同じようなことを検査してくれます ○ 私達のかわりにTSが同じことをやってくれます 18

Slide 19

Slide 19 text

TSで書いてみる ● すると、静的型検査で怒られました ● ソースコードを実行する前に、静的解析によってエラーを発見できました 19 const msg: string = null; console.log("現在のメッセージは" + msg.length + "文字です"); 型注釈:「msgは文字列が入る変数」とい うのをTSに教えている

Slide 20

Slide 20 text

ミニ演習2 ● 上記のコードをPlaygroundにコピペしてみてください ○ どのようなエラーが表示されますか ● msgに適当な文字列を代入するようにして、エラーを解消してみてください ● エラーを解消したプログラムはどのようなJSにコンパイルされますか ○ 特に型注釈の部分はどうなりますか 20 const msg: string = 12345; console.log("現在のメッセージは" + msg.length + "文字です");

Slide 21

Slide 21 text

ありがたみが薄い? ● 今回のコードは簡単だったのでパッと見て不具合がわかった ● しかしこれが何万行もあったら……? ○ ※実際、プロジェクトにおけるTSのコードは数万行になりうる ● TSは、プログラムの各部分を、それが計算する値の種類によって分類することにより、 プログラムがある種の振る舞い(例:nullや数値の.lengthを参照するといった動作)を起 こさないことを検査してくれる*1 ● 人の目で見るより、TSにやってもらったほうが効率的 21 *1:Pierce, Benjamin C. 型システム入門 プログラミング言語と型の理論. 株式会社 オーム社, 2013.(以降TAPLの略称で参照)の p.1にある型システムの定義から一部表現を借りています

Slide 22

Slide 22 text

注意:動的な検査が望ましい場合もある ● 複雑な静的型検査をやろうとすると ○ 難しい型パズルを書くことになったり ■ そのコードを後の人が見てわからないということになったり ○ そもそも型システムの表現力が足りず実現不可能だったり ● それよりは動的に検査してしまって、素直にエラー画面などを表示したほうが簡単という 場合もあります ● やりたいことに応じて適切な手段を取れるようになりましょう 22

Slide 23

Slide 23 text

研修で学ぶこと ● TS(TypeScript)とは何か ● TSの型、コードの書き方 ● その他、周辺知識として知っておいたほうが良いこと 23

Slide 24

Slide 24 text

型注釈 ● TSに「ここは〜〜型ですよ」と教える方法 ● いろいろなところに書けます 24 const a: string = "hoge"; let b: string; var c: string; function greet(name: string): string { return "hello, " + name; } class MyClass { e: string = ""; }

Slide 25

Slide 25 text

型推論 ● その場の状況に合った「一番それっぽい型」をTSが考えてくれる機能 ● 型推論により、型注釈は省略できる場合が多いです ○ 楽! 25 関連キーワード:型再構築

Slide 26

Slide 26 text

しかし、わかりやすさのために型注釈を書くこともある 26 // 自明なので注釈は必須でない const num: number = 123; // dataの型が自明でないので注釈がほしい // 注釈があると、「長い処理」を書き間違ってしまったときにここでエラーが出る // 注釈を消して型推論に任せると、どこか別の場所でエラーが出てデバッグが大変になる かも const data = hogeArray.map(/* 長い処理 */).filter(/* 長い処理 */).reduce(/* さらにつづく...

Slide 27

Slide 27 text

基本的な型 27 参考:https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes-func.html#built-in-types const num: number = 123; const str: string = "hoge"; const bool: boolean = true; const sym: symbol = Symbol("fuga"); const nullObj: null = null; const undef: undefined = undefined; const bigint: bigint = 9007199254740991n;

Slide 28

Slide 28 text

リテラル型 ● 特定の値しか代入できない型 ● 数値リテラル型 / 文字列リテラル型 / booleanリテラル型 28 参考:https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types const oneHundred: 100 = 100; const helloMsg: "hello world" = "hello world"; const trueVal: true = true; const falseVal: false = false;

Slide 29

Slide 29 text

constとletにおける型推論の違い ● 変数がconstの場合、リテラル型に推論される ● 変数がletの場合、リテラル型にはならない 29 aは”hello”以外の値にならない bは今後”hello”以外の値になるかもしれない

Slide 30

Slide 30 text

オブジェクトリテラル型 ● 特定のプロパティを持ったオブジェクトを表す ○ よく使います 30 const obj: { num: number; str: string } = { num: 12345, str: "piyo", };

Slide 31

Slide 31 text

配列型 ● タプル型:Array型の一種 ○ 各要素の型、順番、個数を正確に把握している型 31 const array1: number[] = [1, 2, 3]; // または const array2: Array = [1, 2, 3]; const array3: [number, string, true] = [123, "hello", true];

Slide 32

Slide 32 text

関数型(1/2) ● 型は次のように表される 32 (num: number, bool: boolean) => string // 引数がない時 () => string // 戻り値が無い時 (num: number, bool: boolean) => void

Slide 33

Slide 33 text

関数型(2/2) ● 型注釈で使うとしたらこんなかんじ ● 以下のように書くことも多いです。意味は同じです 33 const fn1: (num: number, bool: boolean) => string = (num, bool) => `${num}${bool}`; const fn2 = (num: number, bool: boolean): string => `${num}${bool}`; function fn3(num: number, bool: boolean): string { return `${num}${bool}`; }

Slide 34

Slide 34 text

any型(1/3) ● どのような値も代入できる 34 const any1: any = 12345; const any2: any = "hogehoge"; const any3: any = (a) => a;

Slide 35

Slide 35 text

any型(2/3) ● ほとんどの操作(例:呼び出す、プロパティにアクセスする、被演算子にする)ができる (!) ● ● したがって慎重に使うべきです ○ コードレビューなどでは相応の理由が求められることも 35 // 静的型検査でおこられない const msg: any = null; // 実行時エラーになる console.log("現在のメッセージは" + msg.length + "文字です");

Slide 36

Slide 36 text

any型(3/3) ● anyあるある ○ 既存のJSプロジェクトにTSを導入している。とりあえず導入だけ済ませて、型は後 から書きたい ○ JSのライブラリをTSで使いたいが、型定義が不足している。自分で定義を書くのは 時間がかかるのでいったんanyにしておきたい ○ 複雑な型エラーが出ていてよくわからん!詳しい人、後で助けてくれ!!! ● うまく使えば非常ハッチとして役立つ ○ しかし「よくわからないから」で放置してしまうと…… 36

Slide 37

Slide 37 text

型エイリアス ● 型に別名を付けられる ● 慣例として、型名は大文字から始める場合が多いです 37 type MyName = string; const myName: MyName = "taro yamada"; type Circle = { pos: { x: number; y: number }; r: number }; const circle: Circle = { pos: { x: 1, y: 2 }, r: 3 };

Slide 38

Slide 38 text

演習1 ● 問題1:次のuser変数に型注釈を付けてください 38 const user = { firstName: "太郎", lastName: "山田", age: 24, favoriteFoods: ["寿司", "ラーメン", "カレー"], hasProgrammingExperience: true, };

Slide 39

Slide 39 text

演習1 ● 問題2:次のようなgetSelfIntroduction関数を書いてみてください ○ 引数:1つ。先程のuserオブジェクト。引数名は任意 ○ 戻り値: string ■ 次のような自己紹介文。[ ]の中は引数によって変化する部分 ■ 私の名前は[姓][名]です。年齢は[年齢]歳です。好きな食べ物は[〜〜と〜〜 と〜〜(〜〜には好きな食べ物配列の各要素が入る。要素数だけ繰り返す)] です。プログラミングの経験[があります or はありません] ● 問題3:getSelfIntroduction関数を実際に実行し、consoleに自己紹介文を出力してみ てください 39

Slide 40

Slide 40 text

Class ● JSにあるClass構文をTSでも使えます ● 宣言したClassは型としても使えます ○ Javaとかと一緒 40

Slide 41

Slide 41 text

Class 41 インターフェースの例の出典:https://ja.wikipedia.org/wiki/%E3%82%A4%E3%83%B3%E3%82%BF%E3%83%95%E3%82%A7%E3%83%BC%E3%82%B9_(%E6%8A%BD%E8%B1%A1%E5%9E%8B) interface Flyable { fly(): void; } class Bird implements Flyable { name: string; constructor(name: string) { this.name = name; } fly() { console.log(`${this.name}「パタパタ」`); } } const bat: Bird = new Bird("コウモリ");

Slide 42

Slide 42 text

JSのClassに無い機能 ● 抽象クラス / 抽象メソッドを定義できます ● Interfaceを実装できます 42

Slide 43

Slide 43 text

Interface vs 型エイリアス(1/2) ● どちらの記法もオブジェクトの型を定義できる 43 他の細かい違いは:https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#differences-between-type-aliases-and-interfaces interface Obj1 { prop: 123; } const obj1: Obj1 = { prop: 123 }; type Obj2 = { prop: 123; }; const obj2: Obj2 = { prop: 123 };

Slide 44

Slide 44 text

Interface vs 型エイリアス(2/2) ● わりとその場のノリで使い分けています ● Interfaceは拡張される。型エイリアスはされない(エラーになる ● ライブラリの型がinterfaceで書かれており、ユーザー側でそれを拡張する……みたいな 場面もたまにある 44 他の細かい違いは:https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#differences-between-type-aliases-and-interfaces interface Hoge { a: number; } interface Hoge { b: string; } どっちも同じ interface Hoge { a: number; b: string; }

Slide 45

Slide 45 text

Union ● 「または」の意味 ● 合併型*1、バリアント型*2、和型*3とも ● Stringのリテラル型と併用すると、列挙型のように扱うこともできる ● (これとは別にenum構文もありますが、あまり利用されません) 45 *1:Unionと同じ意味。*2:タグ付きのような「互いに素」なUnionを特に指す。*3:2つの型に対するバリアント型をいう。TAPL, 11.10節, p.108より type NumOrStr = string | number; type MaybeStr = string | null | undefined; type Colors = "red" | "blue" | "green" | "yellow";

Slide 46

Slide 46 text

Optional ● JavaScriptのプログラムでよくあるバグは、nullまたは未定義の変数に対して、値が存在 する前提でアクセスしてしまうケース ● (余談)Tony Hoareいわく「Null参照は最大の誤りだった。コンパイルですべてチェックで きるようにしたかった」 ○ Null参照:10億ドルの間違い 46

Slide 47

Slide 47 text

朗報:TSではstrictNullChecksがデフォルトで有効 ● nullまたはundefinedを厳密に扱ってくれるオプション ○ 例えばundefinedになりうるオブジェクトのプロパティを参照しようとすると警告してく れます 47 参考:https://www.typescriptlang.org/tsconfig#strictNullChecks

Slide 48

Slide 48 text

「?」を使って「undefinedになりうる」ことを伝える ● 48 interface Hoge { a?: number; } type Fuga = { b?: string; }; function fn(c?: boolean) { return c; }

Slide 49

Slide 49 text

他の表現 ● Unionを使うと同じようなことが実現できます ● 「?」記法の方が短く簡単 49 type maybeNumber = number | undefined;

Slide 50

Slide 50 text

型の絞り込み(1/2) ● Union型は、if文やswitch文によって値をチェックしていくことで、より詳細な型へ絞り込 まれます 50 function fn(a?: number) { // ここでaはnumber | undefined型。以下はおこられる Math.sqrt(a); if (a === undefined) return; // ここではnumber型。以下はおこられない Math.sqrt(a); }

Slide 51

Slide 51 text

型の絞り込み(2/2) ● 他にも以下のような構文で絞り込まれます ○ typeof x === “string” ○ x instanceof HogeClass ○ リテラル型の同値確認( if (x === “some message”) ) ○ obj.x や obj[“x”] や x in objによるプロパティの存在確認 ● 上記だけでは十分でない場合、ユーザー定義の型ガードを使用する手もあります ○ 詳しくは割愛。必要になったら調べましょう 51

Slide 52

Slide 52 text

readonly ● 書き換え操作を型検査で抑止します ○ ランタイムのJSからは書き換え可能 52 type Obj = { readonly prop: string; }; const obj: Obj = { prop: "hello", }; obj.prop = "goodbye"; // おこられる

Slide 53

Slide 53 text

unknown型 / never型 ● unknown型 ○ どのような値も代入できる ○ できる操作は少ない:よく分からない値なので迂闊に扱えない ■ any型の値はそこから何でもできた ■ unknown型の値は、それを具体的な値に絞り込まないと何もできないので安 全 ● never型 ○ どのような値も代入できない ○ 型の絞り込みの結果、到達不能な部分などで現れる 53

Slide 54

Slide 54 text

ジェネリクス(1/4) ● 総称性、パラメータ(パラメトリック)多相とも ● いろいろな型に対応するプログラムが書ける能力 54

Slide 55

Slide 55 text

ジェネリクス(2/4) ● 引数をそのまま返すid関数を作りたいとします ● すべての型に対してid関数のバリエーションを作るのは面倒 55 const id = (arg) => arg; const idBoolean = (arg: boolean): boolean => arg; const idNumber = (arg: number): number => arg; const idString = (arg: string): string => arg; // ...

Slide 56

Slide 56 text

ジェネリクス(3/4) ● unknownと型を付けて実装をまとめてもいいけれど、戻り値の型もunknownになってし まいます 56 const id = (arg: unknown): unknown => arg; // resultはunknown型に const result = id(123);

Slide 57

Slide 57 text

ジェネリクス(4/4) ● ジェネリクスを使うと、以下のようにできます 57 // 型変数Tで引数の型をキャプチャ const id = (arg: T): T => arg; // resultはnumber型 const result = id(123); // または型推論に任せて const result = id(123);

Slide 58

Slide 58 text

演習2 ● 問題1:以下のgetUserName関数を完成させてください ○ ヒント:途中で「object & Record<"name", unknown>」という型を見るかもしれませ ん。これは「{ name: unknown }」のことです ■ 「&」の意味については後述します 58 type User = { name: string }; // User型が与えられれば名前を返す。そうでなければ undefinedを返す function getUserName(maybeUser: unknown): string | undefined { // TODO: ここでmaybeUserを絞り込む return maybeUser.name; }

Slide 59

Slide 59 text

演習2 ● 問題2:以下のprintSelfIntroduction関数で使用されているSubject型を書いてみてくだ さい。関数の仕様は次ページにあります 59 function printSelfIntroduction(subject: Subject) { switch (subject.type) { case "name": console.log(`私の名前は${subject.payload}です`); return; case "favorite_food": console.log(`私の好きな食べ物は${subject.payload.join("と")}です`); return; } }

Slide 60

Slide 60 text

演習2 ● 引数 ○ subject: 自己紹介の主題。typeとpayloadの2つのプロパティをもつ ■ payload: typeによって変わる ■ type: 文字列。”name”または”favorite_food” ● “name”: payloadには名前(文字列)が入る。 ● “favorite_food”: payloadには好きな食べ物の名前の配列(文字列の配 列)が入る。 ● 戻り値 ○ なし 60

Slide 61

Slide 61 text

演習2 ● 問題3:printSelfIntroduction関数で”favorite_food”ケースの実装を忘れてしまったとし ます。現状では何の型エラーも起きません。ケースの実装漏れでエラーが起きるように 関数を改良してください ○ ヒント:すべてのケースが実装されている場合、default節におけるsubjectの型は ……? 61

Slide 62

Slide 62 text

演習2 ● 問題4:ジェネリクスを使って、以下のようなtriple関数を作成してください ● ● 問題5:「triple(triple(triple(123)))」をconsole.logに出力して観察しましょう。また、戻り 値の型はどのようになっているか観察しましょう 62 triple(123) // [number, number, number] triple("hello") // [string, string, string]

Slide 63

Slide 63 text

部分型関係(1/2) ● 例えば次のような型があります ● Animal型の変数にBird型の値を代入しても安全です ● Javaの教科書とかでよく見るやつですね 63 type Animal = { name: string }; type Bird = { name: string; wings: "翼" }; type Dog = { name: string; forefoot: "前足" }; const bird: Bird = { name: "スズメ", wings: "翼" }; const animal: Animal = bird; console.log(`この動物は${animal.name}です`);

Slide 64

Slide 64 text

部分型関係(2/2) ● S型の値をT型の値として扱っても安全であるとき*1 ○ SはTの部分型、TはSの上位型です ○ SとTは部分型関係にあります ■ サブタイプ関係、is-a関係とも ○ S <: Tと書きます(紙面の節約のため。TSの文法ではない) ● 例: ○ BirdはAnimalの部分型、AnimalはBirdの上位型 ○ BirdとAnimalは部分型関係にあります ○ Bird <: Animal ● 再掲:部分型の値は上位型の値として扱っても安全 64 参考:principle of safe substitution(安全代入の原則、または安全代用の原則)、TAPL 15.1節。参考:subsumption principle(包摂原則)、Harper, Robert. Practical foundations for programming languages. Cambridge University Press(PFPL), 2016., Chapter 24。*1:より正確には「型Sの任意の項が、型Tの項が期待されている文脈で安全に使用可能であるとき」、TAPL 15.1節より。

Slide 65

Slide 65 text

部分型関係の性質 ● 反射的 ○ Animal <: Animal ○ Bird <: Bird ○ Dog <: Dog ● 推移的*1 ○ Chihuahua <: Dog <: Animal ○ ならば ○ Chihuahua <: Animal ● 前順序(pre-order)とも 65 参考:TAPL 15.2節 p.142。*1:TSの代入可能関係は一部で推移的ではないようなのでご注意ください。詳細:https://github.com/sititou70/ts-extends-hierarchy。また、部分型関係と代入可能関係の違いについて

Slide 66

Slide 66 text

オブジェクトの部分型関係 ● 規則1:共通するプロパティについても部分型関係でなければならない*1 ● 規則2:上位型にないプロパティが部分型に存在しても良い*2 ● 例: 66 *1:深さ部分型付け(S-RcdDepth)、TAPL p.143より。*2:幅部分型付け(S-RcdWidth)、TAPL p.142より { myName: string; partner: Dog; } <: { partner: Animal }

Slide 67

Slide 67 text

配列の部分型関係 ● S <: T ● ならば ● S[] <: T[] ● です*1 67 *1:これは便利ですが安全ではありません。詳しくは:TypeScriptにおける配列の共変性

Slide 68

Slide 68 text

関数の部分型関係 ● T1 <: S1 かつ S2 <: T2 ● ならば ● (S1) => S2 <: (T1) => T2 ● です ● ちょっと直感的ではないです。後の演習で試して理解するのが早いと思います 68 参考:TAPL 15.2節, p.144

Slide 69

Slide 69 text

部分型関係のグラフ*1 ※今日紹介した型しか載せていません。本当はもう少し複雑です 69 *1:厳密には代入可能関係のグラフです。この図を生成したコード、および完全なグラフは次のリポジトリにあります:https://github.com/sititou70/ts-extends-hierarchy 上位型 部分型

Slide 70

Slide 70 text

部分型関係まとめ ● 以下のようなエラーメッセージを見かけたら ○ type '〜〜' is not assignable to type '〜〜' ● まずは部分型関係を思い出してみましょう ● エラーが出ている部分について、コードが安全かどうか確かめましょう ○ 安全か:部分型の値を上位型の値として扱っているか 70

Slide 71

Slide 71 text

発展:構造的と名前的 ● TSの型システムは構造的。部分型関係は構造の上に直接定義される ● 一方で、Javaなどの型システムは名前的。部分型関係はextendsなどで明示的に宣言さ れる。構造的に互換性があっても、宣言されていない関係は認められない 71 参考:TAPL 19.3節 class Super {} class Sub1 extends Super { String prop; } class Sub2 extends Super { String prop; } class Main { public static void main(String args[]) { // 認められない Sub1 s = new Sub2(); } }

Slide 72

Slide 72 text

型アサーション(as) ● 値の型をキャストできます ● 例*1 72 *1:https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#type-assertions より const myCanvas = document.getElementById("main_canvas") as HTMLCanvasElement;

Slide 73

Slide 73 text

アップキャスト ● source as Targetのとき、Source <: Targetであること ● 例:dog as Animal ● 常に安全 ○ Dogの値をAnimalとして扱っても何の問題もない ● そもそも必要ないことも 73 // as Animalは必要ない const animal: Animal = dog as Animal;

Slide 74

Slide 74 text

ダウンキャスト ● source as Targetのとき、Target <: Sourceであること ● 例:animal as Dog ● 危険! ○ Animalの値をDogとして扱う ○ Dogにしかないプロパティにアクセスしたら実行時エラーになる ● 先程の例のように、相当の理由と自信がある場合のみ使ってください 74 const myCanvas = document.getElementById("main_canvas") as HTMLCanvasElement;

Slide 75

Slide 75 text

部分型関係にない型へのキャストは認められない ● これは認められない ○ dog as Bird ● しかし、いったん上位型を経由することでキャストできてしまいます 75 // これはできる dog as Animal as Bird // またはより一般的に dog as any as Bird dog as unknown as Bird

Slide 76

Slide 76 text

もちろんこれもできる ● めちゃくちゃです ○ 本当の本当にマジで必要な時以外はやらないでください ● この「いったん上位型を経由して部分型関係にない型にキャストすること」は「愚かな キャスト」とも呼ばれます*1 ● ちなみにJavaでは「ClassCastException」として認められません 76 *1:一部の界隈でだけかもしれません。Featherweight Java、TAPL 19.4節より dog as unknown as HTMLCanvasElement

Slide 77

Slide 77 text

Intersection ● 「かつ」の意味 ● Unionの逆 ● 交差型とも 77 *1:S-Inter3規則、TAPL p.161 type A = number & unknown; // number type B = number & never; // never type C = { a: number } & { b: string }; // C <: { a: number } かつ C <: { b: string }であるようなCは?*1 // C = { a: number; b: string }

Slide 78

Slide 78 text

演習3 ● 問題1:Dogの配列がAnimalの配列に代入可能であることを確かめるコードを書いてく ださい 78

Slide 79

Slide 79 text

演習3 ● 問題2:以下のコードを使って、関数の部分型関係を検証してください 79 type Animal = { name: string }; type Dog = { name: string; forefoot: "前足" }; const dog: Dog = { name: "イヌ", forefoot: "前足" }; let dogToAnimal = (dog: Dog): Animal => dog; // TODO: ここでdogToAnimalを、その部分型の関数で代用してみてください // ヒント:関数の部分型関係の定義より、代用する関数の型は次のようにできるでしょう // 引数の型:元の関数における引数の上位型 // 戻り値の型:元の関数における戻り値の部分型 dogToAnimal = ここを埋める; const animal: Animal = dogToAnimal(dog);

Slide 80

Slide 80 text

演習3 ● 問題3:以下はそれぞれどのような型でしょうか。確かめてみてください 80 type D = { prop: { a: number } } & { prop: { b: string } }; type E = number & 12345; type F = "Love" & "Peace";

Slide 81

Slide 81 text

演習3 ● 問題4:asを使って、型検査に合格するが実行時エラーになるコードを書いてみてくださ い 81

Slide 82

Slide 82 text

発展:Utility Types ● 型の取り回しを便利にする型 ● 型を受け取って型を返します*1 ● たま〜〜〜に使います。今回は割愛 82 *1:型演算子とも。ただし高階の型演算子はTSにないです。工夫してそれっぽいものを実現することはできます。参考:https://github.com/gvergnaud/hotscript type A = Partial<{ a: number }>; // { a?: number } type B = Required<{ a?: number }>; // { a: number } type C = Pick<{ a: number; b: string }, "b">; // { b: string } type D = Omit<{ a: number; b: string }, "b">; // { a: number } type E = Readonly<{ a: number }>; // { readonly a: number; } // まだまだある

Slide 83

Slide 83 text

余談:型パズル(1/2) ● TSの型システムはチューリング完全であることが知られています ○ つまり、型検査が停止しないかもしれない(!) ● 実際には、処理系によって制限が設定されており、停止します ○ ○ が、それを回避する型ハックもあります ○ 悪用すると型検査器を落とすこともできます(やめましょう) 83

Slide 84

Slide 84 text

余談:型パズル(2/2) ● 型だけで様々なプログラムが書けます ○ コンパイル後のJSが空(”use strict”とかimport / exportとかしかない) ● 電卓 ● 平方根の近似 ● TSの型検査器自身 84

Slide 85

Slide 85 text

余談:型パズル(2/2) 85 やばい

Slide 86

Slide 86 text

研修で学ぶこと ● TS(TypeScript)とは何か ● TSの型、コードの書き方 ● その他、周辺知識として知っておいたほうが良いこと 86

Slide 87

Slide 87 text

ライブラリの型定義 ● JSのプロジェクトでは、ほぼ確実に外部のライブラリを使用します ● 通常は、npm(Node.jsのパッケージマネージャ)に公開されているライブラリを利用する ケースが多いです ● 外部ライブラリをTSから利用するためには、「型定義ファイル」が必要です 87

Slide 88

Slide 88 text

型定義ファイルとは ● TSコードのうち、型の定義に関する情報だけを抽出したもの ● 拡張子は.d.ts ● 例:「jQueryの型情報が表示されないんだけど、.d.tsファイル入れ忘れてね?」 ○ みたいによく言う ● 演習:適当な型定義ファイルを生成してみましょう ○ Playgroundに適当なTSコードを貼り付けて、.D.TSタブを開いてみる 88

Slide 89

Slide 89 text

ライブラリの型定義はどこから来るのか(1/3) ● 大きく3パターンあります ● パターン1:パッケージに同梱されている ○ ライブラリをnpm installした時点で型定義が使えるようになっています ○ この手のやつは、パッケージのpackage.jsonに「“types”: “index.d.ts”」といった記載 があります ○ npmに以下のようなバッジがあります 89

Slide 90

Slide 90 text

ライブラリの型定義はどこから来るのか(2/3) ● パターン2:第3者が提供してくれている(DefinitelyTyped) ○ npm i @types/(ライブラリ名) でインストールします ○ npmにバッジがあります 90

Slide 91

Slide 91 text

ライブラリの型定義はどこから来るのか(3/3) ● パターン3:自分で定義する ○ Ambient module宣言という手法で自分でライブラリに型定義を与えることができま す ○ 少しマニアックなので今日は触れません ● (パターン4:anyで妥協する) ○ (これも重要な逃げ道) 91

Slide 92

Slide 92 text

型定義が壊れていたら ● 提供された型定義が本体のAPIを網羅していないことも度々ある ○ e.g. 特定の関数の型定義がごっそり抜けてる / anyだらけ / etc... ● コントリビューションチャンス! ○ 型定義を修正するPRを出して世の中に貢献しましょう 92

Slide 93

Slide 93 text

モジュール ● JSには複数のモジュールのスタイルがあります(歴史的事情) ○ ES2015のモジュール ■ 例:import hoge from “module-name”; ■ ECMAScript 2015というバージョンで初めてモジュールに関する構文が仕様化されました ■ それまではJSにモジュールという概念は無かった ○ Common JSのモジュール ■ 例:const hoge = require(“module-name”); ■ ES2015以前から、Node.jsなどで使われていたスタイル ○ 他にも色々 ● JSの実行環境によってモジュールのスタイルが異なるのが現状 ○ したがって、利用するモジュールローダー(モジュールを読み込む仕組み)も使い分ける必要があり ます 93

Slide 94

Slide 94 text

TSのモジュール事情 ● 基本はESスタイルで記述 ● コンパイル時にランタイムで利用したいモジュールローダーを指定すると、うまいこと変換してく れます ● tsconfig.jsonのmodule:利用したいモジュールローダーを指定 ○ commonjs:Node.jsで実行したい場合など ○ esnext:ブラウザのを使う場合など ○ 他の値も選択可能だが、使うケースは正直少ない ● 演習 ○ importとexportを使って適当なTSのコードを書いてください ○ Playgroundのmoduleの設定をcommonjsとesnextで切り替えたとき、生成されるJSはどの ように変化しますか 94

Slide 95

Slide 95 text

ビルド ● JavaScriptのビルドの歴史は割と闇が深い ● 太古の昔 ○ AltJSが流行る前は、開発者が書いた.jsファイルをそのままHTMLのタグで読み 込んでいました ● よりリッチな体験がwebに求められるにつれ、読み込まれるJSファイルは重厚長大化 ● これにともなって、様々な課題が浮上 ○ JSファイルを分割したいが、<script>タグによる読み込み回数は増やしたくない ○ 別言語(AltJS)で開発したい ○ ダウンロードサイズ削減のため、JSファイルを圧縮したい ○ etc… ● いろいろなツールが開発され、吟味され、混乱もあり、いろいろあって …… 95

Slide 96

Slide 96 text

モジュールバンドラという概念が生まれた ● 以下のようなことをやってくれるツール ○ importとexportを解析、必要な複数のJSファイルを1つにまとめる ○ tree shaking:importしたけど利用していないライブラリをバンドル結果から除外 ○ ソースマップ:元のコードとバンドル結果の対応関係を表すファイル.デバッグに役立つ ○ バンドル結果の圧縮、難読化 ○ Alt JSのコンパイル ● バンドラもいろいろある ○ webpack, rollup.js, fusebox, Parcel, esbuild, etc… ● 近年はwebpackが広く使われている印象 96

Slide 97

Slide 97 text

最近だと ● ECMAScriptのimport/export構文やブラウザのといった標準 化が進み、バンドラに頼らなくともモジュールが利用可能に ● とはいえ、import の連鎖が深くなると、ブラウザが最後のモジュールに到達するまでに 時間がかかってしまいます ○ この意味で、ファイルをまとめるモジュールバンドラは当分必要とされつづけると思 われます 97

Slide 98

Slide 98 text

遅延と投機 ● 一概にすべてのJavaScriptファイルを単一ファイルにバンドルすればよい、という訳では ない ● 適切な使い分けが重要です ○ 「事前に必要となることがわかっているモジュール」を投機的にfetchする ○ 「ユーザーにとって本当に必要となるまで」ダウンロードを遅延させる 98

Slide 99

Slide 99 text

周辺のツールチェイン:TypeScript自体の便利機能 ● TypeScript本体に付属しているtscというツール自体にも便利機能があります ○ --noEmit : JSの生成を行なわずに、型検査のみを実行するオプション. CI等に組み 込むと便利 ○ --noUnusedLocals、--noUnusedParameters: 利用されていない変数を検知してエ ラーにするオプション. 冗長なコードの削減になる ● その他にも、tscには様々なオプションが用意されています。公式ドキュメントを見てみる とよいでしょう 99

Slide 100

Slide 100 text

周辺のツールチェイン:ESLint ● AST(抽象構文木)を用いたコードのチェックツール。禁止したいコードパターンをルール として登録して利用します ● ルールの例 ○ consoleを使わないこと ○ 再代入しない変数にはletではなくconstを用いること ● TypeScriptとESLintを併用する場合、 https://github.com/typescript-eslint/typescript-eslintを使います 100

Slide 101

Slide 101 text

周辺のツールチェイン:Prettier ● ソースコードを機械的にフォーマットするためのツール ● 下記のような不毛な議論に左右されなくなります ○ 「文字列リテラルはシングルクォート or ダブルクォート」 ○ 「セミコロンを文の末尾につけるべきか」 ● ファイルの保存時やコミット時に自動的にフォーマットが走る設定にしておくと便利です ○ ファイルの保存時:VSCodeの設定 ○ コミット時:Git Hookの設定、例えばHuskyを使うなど ● Prettierは標準でTypeScriptに対応 101

Slide 102

Slide 102 text

周辺のツールチェイン:自動生成等 ● I/Oやネットワーク通信など、外部との境界ではanyやunknownが発生しがち ○ 実際、外部システムとインターフェースの認識がずれていた、または認識はあって いたけど実装がずれていた、みたいな障害がありがちです ● 使用する通信プロトコルに合わせて、IDL(インターフェース記述言語)からTypeScriptの 型を生成するツールも多数存在します ○ OpenAPI(Swagger), GraphQL, TypeORM, Prisma, etc… 102

Slide 103

Slide 103 text

まとめ:この資料では以下のことを学びました ● TSは ○ Alt JS:JavaScriptにコンパイルされる ○ (静的な)型が付いたJSのスーパーセット ● 周辺には便利なツールがいっぱいある ○ 上手に使いこなしましょう ● 型は友達 103

Slide 104

Slide 104 text

参考文献 ● TypeScriptの公式ドキュメント ○ https://www.typescriptlang.org/docs/ ● Pierce, Benjamin C. 型システム入門 プログラミング言語と型の理論. 株式会社 オーム 社, 2013. ○ TAPLの略称でも参照している 104

Slide 105

Slide 105 text

型パズルでもやって遊んでいてください(Level 1) ● 以下のようなIf型を定義してください 105 type CaseIf1 = If // 'a' type CaseIf2 = If // 'b' 出典:https://github.com/type-challenges/type-challenges/blob/main/questions/00268-easy-if/README.md

Slide 106

Slide 106 text

型パズルでもやって遊んでいてください(Level 2) ● 以下のようなReverse型を定義してください 106 type CaseReverse1 = Reverse<['a', 'b']> // ['b', 'a'] type CaseReverse2 = Reverse<['a', 'b', 'c']> // ['c', 'b', 'a'] 出典:https://github.com/type-challenges/type-challenges/blob/main/questions/03192-medium-reverse/README.md

Slide 107

Slide 107 text

型パズルでもやって遊んでいてください(Level 3) ● 以下のような、0以上の整数の掛け算を行うMultiply型を定義してください ● 入力される数値の範囲は自身で定義して構いません 107 type CaseMultiply1 = Multiply<2, 3> // 6 type CaseMultiply2 = Multiply<4, 5> // 20 type CaseMultiply3 = Multiply<6, 0> // 0

Slide 108

Slide 108 text

型パズルでもやって遊んでいてください(Level 4) ● 以下のような、指定した数までの素数に評価されるPrimes型を定義してください ● 入力される数値の範囲は自身で定義して構いません 108 type CasePrimes1 = Primes<300> // [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293]