Movatterモバイル変換


[0]ホーム

URL:


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

TypeScript入門 2024

Avatar for Recruit Recruit
August 09, 2024

TypeScript入門 2024

2024年度リクルート エンジニアコース新人研修の講義資料です

Avatar for Recruit

Recruit

August 09, 2024
Tweet

More Decks by Recruit

See All by Recruit

Other Decks in Technology

See All in Technology

Featured

See All Featured

Transcript

  1. TypeScript入門 2024 雫石 卓耶(@sititou70)

  2. 自己紹介 • 名前:雫石 卓耶(しずくいし たくや)(@sititou70) • 2021年に新卒でリクルートに入社しました • 所属:横断エンジニアリング部 アプリケーション・ソ リューション・グループ(ASG)

    • 普段:TypeScript、React、Next.jsでWebアプリケー ションを書いています • 趣味:型パズルなど • よろしくお願いします 2 フローリングの アイコンが私
  3. 研修で学ぶこと • TS(TypeScript)とは何か(座学9、演習1) • TSの型、コードの書き方(座学5、演習5) • その他、周辺知識として知っておいたほうが良いこと(ほぼ座学のみ) 3

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

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

    もしパズルが解けたら、せっかくなので研修の最後にでも共有してほしいです 5
  6. 研修で学ぶこと • TS(TypeScript)とは何か • TSの型、コードの書き方 • その他、周辺知識として知っておいたほうが良いこと 6

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

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

  9. 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
  10. Alt JS:JavaScriptにコンパイルされる(3/3) • 古いブラウザでも動くようにコンパイルすることもできます • Downlevelingといいます ◦ 例:async / awaitはそのままではIEでは動作しないが、TSが頑張ってコー

    ドを変換してくれる • もちろん、「まったくDownlevelingしない」ということもできます ◦ 例:tsは型検査だけで、コンパイルはBabelなど別のツールに任せる 10
  11. TSは 「(静的な)型が付いたJSのスーパーセット」 11

  12. JSのスーパーセットとは • すべてのJSのコードは、文法的に有効なTSのコードでもあります ◦ 例:以下のようなJSのコードがあります ◦ これをTSのPlaygroundにそのままコピペして動かせます • JSから地続きで学習できるというメリット •

    (JSの良くない部分を引き継ぐ場合もあるというデメリット) 12 const msgString = "hello world"; console.log("現在のメッセージは" + msgString.length + "文字です");
  13. ミニ演習1 1. TS Playgroundを開きます 2. TS Config -> Type Checking

    -> strictが有効になっていることを確認します 3. 上記のコードを実際にTS Playgroundへコピペしてみてください ◦ 実行できますか ◦ コンパイルの結果、どのようなJSになりますか ▪ 右側の.JSタブから確認します 13 const msgString = "hello world"; console.log("現在のメッセージは" + msgString.length + "文字です");
  14. TS Playgroundのおすすめの設定 1. Pluginsタブ -> Format On Saveを有効化、リロード 2. Format

    On Saveタブで以下を有効化 ◦ Enable format on save ◦ Prevent copy link on save 3. Command + sでフォーマットされる 14
  15. TSは 「(静的な)型が付いたJSのスーパーセット」 15

  16. JSは動的な検査を行う言語(1/2) • 実行するまでエラーがわからないのが辛い • 1行目を次のように書き間違えたとします • 実行するとエラーになります • 2行目の「msgString.length」を実行しようとしてエラーになりました 16

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

    = 12345; console.log("現在のメッセージは" + msgString.length + "文字です"); 現在のメッセージはundefined文字です
  18. 開発・テスト時に気づけるか……? • 気づけないとユーザーのブラウザ上で実行時エラーになります ◦ やばい 18

  19. いやいや • 「そもそもmsgStringは文字列が入る変数なんだから、nullや数値を代入してい る1行目の時点でおかしいでしょ」 ◦ ……と、思いましたよね? ◦ msgStringという変数名から「この変数にはなんらかの文字列(文字列”型” の値)を入れるぞ」という作者の気持ちが伝わってきたからです •

    TSの静的型検査器も同じようなことを検査してくれます ◦ 私達のかわりにTSが同じことをやってくれます 19
  20. TSで書いてみる • すると、静的型検査で怒られました • ソースコードを実行する前に、静的解析によってエラーを発見できました 20 const msg: string =

    null; console.log("現在のメッセージは" + msg.length + "文字です"); 型注釈:「msgは文字列が入る変数」 というのをTSに教えている
  21. ミニ演習2 • 上記のコードをPlaygroundにコピペしてみてください ◦ どのようなエラーが表示されますか • msgに適当な文字列を代入するようにして、エラーを解消してみてください • エラーを解消したプログラムはどのようなJSにコンパイルされますか ◦

    特に型注釈の部分はどうなりますか 21 const msg: string = 12345; console.log("現在のメッセージは" + msg.length + "文字です");
  22. ありがたみが薄い? • 今回のコードは簡単だったのでパッと見て不具合がわかった • しかしこれが何万行もあったら……? ◦ ※実際、プロジェクトにおけるTSのコードは数万行になりうる • TSは、プログラムの各部分を、それが計算する値の種類によって分類することに より、プログラムがある種の振る舞い(例:nullや数値の.lengthを参照すると

    いった動作)を起こさないことを検査してくれる*1 • 人の目で見るより、TSにやってもらったほうが効率的 22 *1:Pierce, Benjamin C. 型システム入門 プログラミング言語と型の理論. 株式会社 オーム社, 2013.(以降TAPLの略称で参照)の p.1にある型システムの定義から一部表現を借りています
  23. 注意:動的な検査が望ましい場合もある • 複雑な静的型検査をやろうとすると ◦ 難しい型パズルを書くことになったり ▪ そのコードを後の人が見てわからないということになったり ◦ そもそも型システムの表現力が足りず実現不可能だったり •

    それよりは動的に検査してしまって、素直にエラー画面などを表示したほうが簡 単という場合もあります • やりたいことに応じて適切な手段を取れるようになりましょう 23
  24. 研修で学ぶこと • TS(TypeScript)とは何か • TSの型、コードの書き方 • その他、周辺知識として知っておいたほうが良いこと 24

  25. 型注釈 • TSに「ここは〜〜型ですよ」と教える方法 • いろいろなところに書けます 25 const a: string =

    "hoge"; let b: string; var c: string; function greet(name: string): string { return "hello, " + name; } class MyClass { e: string = ""; }
  26. 型推論 • その場の状況に合った「一番それっぽい型」をTSが考えてくれる機能 • 型推論により、型注釈は省略できる場合が多いです ◦ 楽! 26 関連キーワード:型再構築

  27. しかし、わかりやすさのために型注釈を書くこともある 27 *1:最近のエディタの中には、推論された型をinlay hintsによって表示してくれるものもある // 自明なので注釈は必須でない const num: number =

    123; // dataの型が自明でないので注釈がほしい*1 // 注釈があると、「長い処理」を書き間違ってしまったときにここでエラーが出る // 注釈を消して型推論に任せると、どこか別の場所でエラーが出てデバッグが大変になる かも const data = hogeArray.map(/* 長い処理 */).filter(/* 長い処理 */).reduce(/* さらにつづく...
  28. 基本的な型 28 参考: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;
  29. リテラル型 • 特定の値しか代入できない型 • 数値リテラル型 / 文字列リテラル型 / booleanリテラル型 29

    参考: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;
  30. constとletにおける型推論の違い • 変数がconstの場合、リテラル型に推論される • 変数がletの場合、リテラル型にはならない 30 aは”hello”以外の値にならない bは今後”hello”以外の値になるかもしれない

  31. オブジェクトリテラル型 • 特定のプロパティを持ったオブジェクトを表す ◦ よく使います 31 const obj: { num:

    number; str: string } = { num: 12345, str: "piyo", };
  32. 配列型 • タプル型:Array型の一種 ◦ 各要素の型、順番、個数を正確に把握している型 32 const array1: number[] =

    [1, 2, 3]; // または const array2: Array<number> = [1, 2, 3]; const array3: [number, string, true] = [123, "hello", true];
  33. 関数型(1/2) • 型は次のように表される 33 (num: number, bool: boolean) => string

    // 引数がない時 () => string // 戻り値が無い時 (num: number, bool: boolean) => void
  34. 関数型(2/2) • 型注釈で使うとしたらこんなかんじ • 以下のように書くことも多いです。意味は同じです 34 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}`; }
  35. any型(1/3) • どのような値も代入できる 35 const any1: any = 12345; const

    any2: any = "hogehoge"; const any3: any = (a) => a;
  36. any型(2/3) • ほとんどの操作(例:呼び出す、プロパティにアクセスする、被演算子にする) ができる(!) • • したがって慎重に使うべきです ◦ コードレビューなどでは相応の理由が求められることも 36

    // 静的型検査でおこられない const msg: any = null; // 実行時エラーになる console.log("現在のメッセージは" + msg.length + "文字です");
  37. any型(3/3) • anyあるある ◦ 既存のJSプロジェクトにTSを導入している。とりあえず導入だけ済ませ て、型は後から書きたい ◦ JSのライブラリをTSで使いたいが、型定義が不足している。自分で定義を 書くのは時間がかかるのでいったんanyにしておきたい ◦

    複雑な型エラーが出ていてよくわからん!詳しい人、後で助けてくれ!!! • うまく使えば非常ハッチとして役立つ ◦ しかし「よくわからないから」で放置してしまうと…… 37
  38. 型エイリアス • 型に別名を付けられる • 慣例として、型名は大文字から始める場合が多いです 38 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 };
  39. 演習1 • 問題1:次のuser変数に型注釈を付けてください 39 const user = { firstName: "太郎",

    lastName: "山田", age: 24, favoriteFoods: ["寿司", "ラーメン", "カレー"], hasProgrammingExperience: true, };
  40. 演習1 • 問題2:次のようなgetSelfIntroduction関数を書いてみてください ◦ 引数:1つ。先程のuserオブジェクト。引数名は任意 ◦ 戻り値: string ▪ 次のような自己紹介文。[

    ]の中は引数によって変化する部分 ▪ 私の名前は[姓][名]です。年齢は[年齢]歳です。好きな食べ物は[〜〜 と〜〜と〜〜(〜〜には好きな食べ物配列の各要素が入る。要素数だけ 繰り返す)]です。プログラミングの経験[があります or はありません] • 問題3:getSelfIntroduction関数を実際に実行し、consoleに自己紹介文を出力 してみてください 40
  41. Class • JSにあるClass構文をTSでも使えます • 宣言したClassは型としても使えます ◦ Javaとかと一緒 41

  42. Class 42 インターフェースの例の出典: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("コウモリ");
  43. JSのClassに無い機能 • 抽象クラス / 抽象メソッドを定義できます • Interfaceを実装できます 43

  44. Interface vs 型エイリアス(1/2) • どちらの記法もオブジェクトの型を定義できる 44 他の細かい違いは: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 };
  45. Interface vs 型エイリアス(2/2) • わりとその場のノリで使い分けています • Interfaceは拡張される。型エイリアスはされない(エラーになる • ライブラリの型がinterfaceで書かれており、ユーザー側でそれを拡張する……み たいな場面もたまにある

    45 他の細かい違いは: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; }
  46. Union • 「または」の意味 • 合併型*1、バリアント型*2、和型*3とも • Stringのリテラル型と併用すると、列挙型のように扱うこともできる • (これとは別にenum構文もありますが、あまり利用されません) 46

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

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

  49. 「?」を使って「undefinedになりうる」ことを伝える • 49 interface Hoge { a?: number; } type

    Fuga = { b?: string; }; function fn(c?: boolean) { return c; }
  50. 他の表現 • Unionを使うと同じようなことが実現できます*1 • 「?」記法の方が短く簡単 50 *1:厳密には、プロパティが省略可能であることとundefinedになり得ることは違います。「?」記法は、場合によっては安全でないコードが書けてしまうため、undefinedとのUnionを使用すべきという意見もあります。プロジェクトの方針を確認しましょう。 type maybeNumber =

    number | undefined;
  51. 型の絞り込み(1/2) • Union型は、if文やswitch文によって値をチェックしていくことで、より詳細な 型へ絞り込まれます 51 function fn(a?: number) { //

    ここでaはnumber | undefined型。以下はおこられる Math.sqrt(a); if (a === undefined) return; // ここではnumber型。以下はおこられない Math.sqrt(a); }
  52. 型の絞り込み(2/2) • 他にも以下のような構文で絞り込まれます ◦ typeof x === “string” ◦ x

    instanceof HogeClass ◦ リテラル型の同値確認( if (x === “some message”) ) ◦ obj.x や obj[“x”] や x in objによるプロパティの存在確認 • 上記だけでは十分でない場合、ユーザー定義の型ガードを使用する手もあります ◦ 詳しくは割愛。必要になったら調べましょう 52
  53. unknown型 / never型 • unknown型 ◦ どのような値も代入できる ◦ できる操作は少ない:よく分からない値なので迂闊に扱えない ▪

    any型の値はそこから何でもできた ▪ unknown型の値は、それを具体的な値に絞り込まないと何もできないの で安全 • never型 ◦ どのような値も代入できない ◦ 型の絞り込みの結果、到達不能な部分などで現れる 53
  54. 型の絞り込み:少し複雑な例 54 type User = { name: string }; //

    User型が与えられれば名前を返す。そうでなければ undefinedを返す function getUserName(maybeUser: any): string | undefined { if (typeof maybeUser !== "object") return; // maybeUserはobject | null型 if (maybeUser === null) return; // maybeUserはobject型 if (!("name" in maybeUser)) return; // maybeUserは{ name: unknown }型 if (typeof maybeUser.name !== "string") return; return maybeUser.name; }
  55. readonly • 書き換え操作を型検査で抑止します ◦ ランタイムのJSからは書き換え可能 55 type Obj = {

    readonly prop: string; }; const obj: Obj = { prop: "hello", }; obj.prop = "goodbye"; // おこられる
  56. ジェネリクス(1/4) • 総称性、パラメータ(パラメトリック)多相とも • いろいろな型に対応するプログラムが書ける能力 56

  57. ジェネリクス(2/4) • 引数をそのまま返すid関数を作りたいとします • すべての型に対してid関数のバリエーションを作るのは面倒 57 const id = (arg)

    => arg; const idBoolean = (arg: boolean): boolean => arg; const idNumber = (arg: number): number => arg; const idString = (arg: string): string => arg; // ...
  58. ジェネリクス(3/4) • unknownと型を付けて実装をまとめても良いですが、戻り値の型もunknownに なってしまいます 58 const id = (arg: unknown):

    unknown => arg; const result = id(123); // resultはunknown型
  59. ジェネリクス(4/4) • ジェネリクスを使うと、以下のようにできます 59 // 型変数Tで引数の型をキャプチャ const id = <T>(arg:

    T): T => arg; const result = id<number>(123); // resultはnumber型 // または型推論に任せて const result = id(123);
  60. 演習2 • 問題1:以下のprintSelfIntroduction関数で使用されているSubject型を書いて みてください。関数の仕様は次ページにあります 60 function printSelfIntroduction(subject: Subject) { switch

    (subject.kind) { case "name": console.log(`私の名前は${subject.payload}です`); return; case "favorite_food": console.log(`私の好きな食べ物は${subject.payload.join("と")}です`); return; } }
  61. 演習2 • 引数 ◦ subject: 自己紹介の主題。kindとpayloadの2つのプロパティをもつ ▪ kind: 文字列。”name”または”favorite_food” •

    “name”:payloadには名前(文字列)が入る。 • “favorite_food”:payloadには好きな食べ物の名前の配列(文字列 の配列)が入る。 ▪ payload: kindによって変わる • 戻り値 ◦ なし 61
  62. 演習2 • 問題2:printSelfIntroduction関数で”favorite_food”ケースの実装を忘れてし まったとします。現状では何の型エラーも起きません。ケースの実装漏れでエ ラーが起きるように関数を改良してください ◦ ヒント:すべてのケースが実装されている場合、default節におけるsubject の型は……? 62

  63. 演習2 • 問題3:ジェネリクスを使って、以下のようなtriple関数を作成してください 63 const result1 = triple(123); // result1は[number,

    number, number]型 const result2 = triple("hello"); // result2は[string, string, string]型
  64. 部分型関係(1/4) • サブタイプ関係、is-a関係とも • 例えば次のような型があります 64 type Animal = {

    name: string }; type Bird = { name: string; wings: "翼" }; type Dog = { name: string; forefoot: "前足" };
  65. 部分型関係(2/4) • Animal型の変数にBird型の値を代入できます 65 type Animal = { name: string

    }; type Bird = { name: string; wings: "翼" }; const bird: Bird = { name: "スズメ", wings: "翼" }; const animal: Animal = bird; // できる console.log(`この動物は${animal.name}です`); // エラーにならない
  66. 部分型関係(3/4) • 逆はできません 66 type Animal = { name: string

    }; type Bird = { name: string; wings: "翼" }; const animal: Animal = { name: "スズメ" }; const bird: Bird = animal; // できない(型エラー) // もし↑を許すと、↓のようなコードが書けて、実行時エラーになる console.log(`この動物は${bird.wings}です`);
  67. 部分型関係(4/4) • 「S型の値」を「T型の値」として扱っても安全であるとき*1 ◦ SはTの部分型、TはSの上位型です ◦ 以降、S ⊆ Tと書きます*2 ◦

    「S型の値の集合」は、「T型の値の集合」の部分集合 と考えると分かりやすいかもしれません • 例:Birdの値をAnimalの値として扱っても安全なので ◦ BirdはAnimalの部分型、AnimalはBirdの上位型 ◦ Bird ⊆ Animal • 部分型関係は、変数への代入や、関数への引数の割り当てなど、プログラムの 様々な箇所で検査されます*3 67 参考: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節より。*2:一般的には「<:」という記号が用いられるのですが、本資料では簡単のために部分集合意味論をベースとして、このような表記にしまし た。*3:より正確には、割り当て可能関係が使用されています。 Animal Bird Dog
  68. 部分型関係の性質 • 反射的 ◦ Animal ⊆ Animal ▪ Animal型の変数に、Animal型の値を代入できる ◦

    Dog ⊆ Dog • 推移的*1 ◦ Chihuahua ⊆ Dog ⊆ Animal ◦ ならば ◦ Chihuahua ⊆ Animal • 前順序(pre-order)とも 68 参考:TAPL 15.2節 p.142。*1:TSの代入可能関係は一部で推移的ではないようなのでご注意ください。詳細:https://github.com/sititou70/ts-extends-hierarchy。また、部分型関係と代入可能関係の違いについて Animal Dog Chihuahua
  69. オブジェクトの部分型関係(1/2) • 規則1:上位型にないプロパティが部分型に存在しても良い*1 69 *2:幅部分型付け(S-RcdWidth)、TAPL p.142より type Animal = {

    name: string }; type Bird = { name: string; wings: "翼" }; const bird: Bird = { name: "スズメ", wings: "翼" }; const animal: Animal = bird; // Bird ⊆ Animalなので許される console.log(animal.name); // エラーにならない
  70. オブジェクトの部分型関係(2/2) • 規則2:共通するプロパティについても部分型関係でなければならない*1 70 *1:深さ部分型付け(S-RcdDepth)、TAPL p.143より type Animal = {

    name: string }; type Bird = { name: string; wings: "翼" }; type AnimalHouse = { resident: Animal }; type BirdHouse = { resident: Bird }; const birdHouse: BirdHouse = { resident: { name: "スズメ", wings: "翼" } }; const animalHouse: AnimalHouse = birdHouse; // BirdHouse ⊆ AnimalHouseなので許 される console.log(`この家の住人は${animalHouse.resident.name}です`);
  71. 配列の部分型関係 • Bird ⊆ Animal ならば Bird[] ⊆ Animal[] •

    S ⊆ T ならば S[] ⊆ T[] *1 71 *1:これは便利ですが安全ではありません。詳しくは:TypeScriptにおける配列の共変性 type Animal = { name: string }; type Bird = { name: string; wings: "翼" }; const birdArray: Bird[] = [{ name: "スズメ", wings: "翼" }]; const animalArray: Animal[] = birdArray; // Bird[] ⊆ Animal[]なので許される for (const animal of animalArray) { console.log(`この動物は${animal.name}です`); }
  72. 関数の部分型関係(1/8) • printBirdは、Birdを受け取ります 72 type Animal = { name: string

    }; type Bird = { name: string; wings: "翼" }; let printBird = (bird: Bird) => console.log(bird.name); const bird: Bird = { name: "スズメ", wings: "翼" }; printBird(bird);
  73. 関数の部分型関係(2/8) • printBirdをprintAnimalで置き換えてもエラーになりません 73 type Animal = { name: string

    }; type Bird = { name: string; wings: "翼" }; let printBird = (bird: Bird) => console.log(bird.name); const printAnimal = (animal: Animal) => console.log(animal.name); printBird = printAnimal; const bird: Bird = { name: "スズメ", wings: "翼" }; printBird(bird); (animal: Animal) => void の値を (bird: Bird) => void の値として扱っても安全
  74. 関数の部分型関係(3/8) • getAnimalは、Animalを返します 74 type Animal = { name: string

    }; type Bird = { name: string; wings: "翼" }; let getAnimal = (): Animal => ({ name: "スズメ" }); const animal = getAnimal(); console.log(`動物の名前: ${animal.name}`);
  75. 関数の部分型関係(4/8) • getAnimalをgetBirdで置き換えてもエラーになりません 75 type Animal = { name: string

    }; type Bird = { name: string; wings: "翼" }; let getAnimal = (): Animal => ({ name: "スズメ" }); const getBird = (): Bird => ({ name: "スズメ", wings: "翼" }); getAnimal = getBird; const animal = getAnimal(); console.log(`動物の名前: ${animal.name}`); () => Bird の値を () => Animal の値として扱っても安全
  76. 関数の部分型関係(5/8) Bird ⊆ Animalであるとき • (animal: Animal) => void の値を

    (bird: Bird) => void の値として扱っても安全 ◦ • () => Bird の値を () => Animal の値として扱っても安全 ◦ 76
  77. 関数の部分型関係(6/8) Bird ⊆ Animalであるとき • (animal: Animal) => void ⊆

    (bird: Bird) => void ◦ • () => Bird ⊆ () => Animal ◦ 77
  78. 関数の部分型関係(7/8) Bird ⊆ Animalであるとき • (animal: Animal) => void ⊆

    (bird: Bird) => void ◦ 引数に着目すると、部分型の方に、上位型のAnimalが現れている(反変) • () => Bird ⊆ () => Animal ◦ 戻り値については、部分型の方に、部分型のBirdが現れている(共変) 78
  79. 関数の部分型関係(8/8) より一般的に、2つの関数型「(S1) => S2」と「(T1) => T2」について • T1 ⊆ S1

    かつ S2 ⊆ T2 • ならば • (S1) => S2 ⊆ (T1) => T2 79 参考:TAPL 15.2節, p.144
  80. 部分型関係のグラフ*1 ※今日紹介した型しか載せていません。本当はもう少し複雑です 80 *1:厳密には割り当て可能関係のグラフです。この図を生成したコード、および完全なグラフは次のリポジトリにあります:https://github.com/sititou70/ts-extends-hierarchy 上位型 部分型

  81. 部分型関係まとめ • 以下のようなエラーメッセージを見かけたら ◦ type '〜〜' is not assignable to

    type '〜〜' • まずは部分型関係を思い出してみましょう • エラーが出ている部分について、部分型の値を上位型の値として扱っているか確 かめましょう 81
  82. 発展:名前的と構造的(1/3) • Javaなどの型システムは名前的 ◦ 型には必ず名前がある ▪ 例:class Animal { …

    ◦ 部分型関係はソースコード上に明示的に現れる ▪ 例:class Bird extends Animal { … • TSの型システムは構造的 ◦ 名前の無い型を書ける ▪ 例:{name: string} 型 ◦ 部分型関係は型の構造上に直接的に定義される 82 参考:TAPL 19.3節
  83. 発展:名前的と構造的(2/3) • 部分型関係に由来するエラーが発生したとき「部分型の値を上位型の値として 扱っているか」確かめましょうと言いました ◦ 名前的な型システムでは:ソースコードを確認すれば書かれている ◦ 構造的な型システムでは:型の構造を確認し、関係性を確認する ▪ 部分型関係のルールを知らないとむずかしい

    83
  84. 発展:名前的と構造的(3/3) 名前的部分型では、構造的に互換性があっても、宣言されていない関係は認められな い 84 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(); } }
  85. 型アサーション(as) • 値の型をキャストできます • 例*1 85 *1:https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#type-assertions より const myCanvas

    = document.getElementById("main_canvas") // myCanvasはHTMLElement | null型 const myCanvas = document.getElementById("main_canvas") as HTMLCanvasElement; // myCanvasはHTMLCanvasElement型
  86. アップキャスト • source as Targetのとき、Source ⊆ Targetであること • 例:dog as

    Animal • 常に安全 ◦ Dogの値をAnimalとして扱っても何の問題もない • そもそも必要ないことも 86 // as Animalは必要ない const animal: Animal = dog as Animal;
  87. ダウンキャスト • source as Targetのとき、Target ⊆ Sourceであること • 例:animal as

    Dog • 危険! ◦ Animalの値をDogとして扱う ◦ Dogにしかないプロパティにアクセスしたら実行時エラーになる • 先程の例のように、相当の理由と自信がある場合のみ使ってください 87 const myCanvas = document.getElementById("main_canvas") as HTMLCanvasElement;
  88. 部分型関係にない型へのキャストは認められない • これは認められない ◦ dog as Bird • しかし、いったん上位型を経由することでキャストできてしまいます 88

    // これはできる dog as Animal as Bird // またはより一般的に dog as any as Bird dog as unknown as Bird
  89. もちろんこれもできる • めちゃくちゃです • この「いったん上位型を経由して部分型関係にない型にキャストすること」は 「愚かなキャスト」とも呼ばれます*1 • ちなみにJavaでは「ClassCastException」として認められません 89 *1:一部の界隈でだけかもしれません。Featherweight

    Java、TAPL 19.4節より dog as unknown as HTMLCanvasElement
  90. Intersection(1/2) • 「かつ」の意味 • Unionの逆 • 交差型とも 90 type A

    = number & unknown; // Aはnumber型 type B = number & never; // Bはnever型
  91. Intersection(2/2) 91 *1:S-Inter3規則、TAPL p.161 type C = { a: number

    } & { b: string }; // C ⊆ { a: number } かつ C ⊆ { b: string }であるようなCを考える*1 // したがって、 // Cは{ a: number; b: string }型
  92. 演習3 • 問題1: ◦ filterBirdsは、Birdの配列をフィルタリングする関数です。 ▪ 引数: • birds:Birdの配列 •

    shouldExtract:birdsの各要素を取り出すかを判定する関数 ▪ 戻り値:フィルタリングされたBirdの配列 ◦ filterBirdsに渡せるshouldExtractの実装を、次ページに示すサンプルコードの他 に2つ挙げてください ▪ 各実装でshouldExtractの型は異なる必要があります ▪ 実行時にJSのエラーになってはいけません ▪ filterの結果は変わっても良いです 92
  93. 演習3 93 type Animal = { name: string }; type

    Bird = { name: string; wings: "翼" }; const birds: Bird[] = [ { name: "スズメ", wings: "翼" }, { name: "カラス", wings: "翼" }, ]; const shouldExtract = (bird: Bird): boolean => bird.name === "スズメ"; console.log(filterBirds(birds, shouldExtract)); type Animal = { name: string }; type Bird = { name: string; wings: "翼" }; const birds: Bird[] = [ { name: "スズメ", wings: "翼" }, { name: "カラス", wings: "翼" }, ]; const shouldExtract = (bird: Bird): boolean => bird.name === "スズメ"; console.log(filterBirds(birds, shouldExtract)); const filterBirds = ( birds: Bird[], shouldExtract: (bird: Bird) => boolean ): Bird[] => { const filteredBirds: Bird[] = []; for (const bird of birds) if (shouldExtract(bird)) filteredBirds.push(bird); return filteredBirds; };
  94. 演習3 • 問題2:以下はそれぞれどのような型でしょうか。確かめてみてください 94 type D = { prop: {

    a: number } } & { prop: { b: string } }; type E = number & 12345; type F = "Love" & "Peace";
  95. 演習3 • 問題3:asを使って、型検査をパスするが実行時エラーになるコードを書いてみ てください 95

  96. 発展:Utility Types • 型の取り回しを便利にする型 • 型を受け取って型を返します*1 • たま〜〜〜に使います。今回は割愛 96 *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; } // まだまだある
  97. 余談:型パズル(1/4) • TSの型システムはチューリング完全であることが知られています ◦ つまり、型検査が停止しないかもしれない(!) • 実際には、処理系によって制限が設定されており、停止します ◦ ◦ が、それを回避する型ハックもあります

    ◦ 悪用すると型検査器を落とすこともできます(やめましょう) 97
  98. 余談:型パズル(2/4) • 型だけで様々なプログラムが書けます ◦ コンパイル後のJSが空(”use strict”とかimport / exportとかしかない) • 電卓

    98
  99. 余談:型パズル(3/4) • Schemeインタプリタ 99

  100. 余談:型パズル(4/4) • TSの型検査器自身 100

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

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

  103. 型定義ファイルとは • TSコードのうち、型の定義に関する情報だけを抽出したもの • 拡張子は.d.ts • 例:「jQueryの型情報が表示されないんだけど、.d.tsファイル入れ忘れてね?」 ◦ みたいによく言う •

    演習:適当な型定義ファイルを生成してみましょう ◦ Playgroundに適当なTSコードを貼り付けて、.D.TSタブを開いてみる 103
  104. ライブラリの型定義はどこから来るのか(1/3) • 大きく3パターンあります • パターン1:パッケージに同梱されている ◦ ライブラリをnpm installした時点で型定義が使えるようになっています ◦ この手のやつは、パッケージのpackage.jsonに「“types”:

    “index.d.ts”」と いった記載があります ◦ npmに以下のようなバッジがあります 104
  105. ライブラリの型定義はどこから来るのか(2/3) • パターン2:第3者が提供してくれている(DefinitelyTyped) ◦ npm i @types/(ライブラリ名) でインストールします ◦ npmにバッジがあります

    105
  106. ライブラリの型定義はどこから来るのか(3/3) • パターン3:自分で定義する ◦ Ambient module宣言という手法で自分でライブラリに型定義を与えること ができます ◦ 少しマニアックなので今日は触れません •

    (パターン4:anyで妥協する) ◦ (これも重要な逃げ道) 106
  107. 型定義が壊れていたら • 提供された型定義が本体のAPIを網羅していないことも度々ある ◦ e.g. 特定の関数の型定義がごっそり抜けてる / anyだらけ / etc...

    • コントリビューションチャンス! ◦ 型定義を修正するPRを出して世の中に貢献しましょう 107
  108. モジュール • JSには複数のモジュールのスタイルがあります(歴史的事情) ◦ ES2015のモジュール ▪ 例:import hoge from “module-name”;

    ▪ ECMAScript 2015というバージョンで初めてモジュールに関する構文が仕様化され ました ▪ それまではJSにモジュールという概念は無かった ◦ Common JSのモジュール ▪ 例:const hoge = require(“module-name”); ▪ ES2015以前から、Node.jsなどで使われていたスタイル ◦ 他にも色々 • JSの実行環境によってモジュールのスタイルが異なるのが現状 ◦ したがって、利用するモジュールローダー(モジュールを読み込む仕組み)も使い分ける 必要があります 108
  109. TSのモジュール事情 • 基本はESスタイルで記述 • コンパイル時にランタイムで利用したいモジュールローダーを指定すると、うまいこと 変換してくれます • tsconfig.jsonのmodule:利用したいモジュールローダーを指定 ◦ commonjs:Node.jsで実行したい場合など

    ◦ esnext:ブラウザの<script type=“module”>を使う場合など ◦ 他の値も選択可能だが、使うケースは正直少ない • 演習 ◦ importとexportを使って適当なTSのコードを書いてください ◦ Playgroundのmoduleの設定をcommonjsとesnextで切り替えたとき、生成される JSはどのように変化しますか 109
  110. ビルド • JavaScriptのビルドの歴史は割と闇が深い • 太古の昔 ◦ AltJSが流行る前は、開発者が書いた.jsファイルをそのままHTMLの<script>タグ で読み込んでいました • よりリッチな体験がwebに求められるにつれ、読み込まれるJSファイルは重厚長大化

    • これにともなって、様々な課題が浮上 ◦ JSファイルを分割したいが、<script>タグによる読み込み回数は増やしたくない ◦ 別言語(AltJS)で開発したい ◦ ダウンロードサイズ削減のため、JSファイルを圧縮したい ◦ etc… • いろいろなツールが開発され、吟味され、混乱もあり、いろいろあって…… 110
  111. モジュールバンドラという概念が生まれた • 以下のようなことをやってくれるツール ◦ importとexportを解析、必要な複数のJSファイルを1つにまとめる ◦ tree shaking:importしたけど利用していないライブラリをバンドル結果から除外 ◦ ソースマップ:元のコードとバンドル結果の対応関係を表すファイル.デバッグに

    役立つ ◦ バンドル結果の圧縮、難読化 ◦ Alt JSのコンパイル • バンドラもいろいろある ◦ webpack、rollup.js、FuseBox、Parcel、esbuild、Turbopack、etc… • 近年はwebpackが広く使われている印象 111
  112. 最近だと • ECMAScriptのimport/export構文やブラウザの<script type=“module”>と いった標準化が進み、バンドラに頼らなくともモジュールが利用可能に • とはいえ、import の連鎖が深くなると、ブラウザが最後のモジュールに到達す るまでに時間がかかってしまいます ◦

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

    113
  114. 周辺のツールチェイン:TypeScript自体の便利機能 • TypeScript本体に付属しているtscというツール自体にも便利機能があります ◦ --noEmit : JSの生成を行なわずに、型検査のみを実行するオプション. CI等 に組み込むと便利 ◦

    --noUnusedLocals、--noUnusedParameters: 利用されていない変数を検知 してエラーにするオプション. 冗長なコードの削減になる • その他にも、tscには様々なオプションが用意されています。公式ドキュメントを 見てみるとよいでしょう 114
  115. 周辺のツールチェイン:ESLint • AST(抽象構文木)を用いたコードのチェックツール。禁止したいコードパター ンをルールとして登録して利用します • ルールの例 ◦ consoleを使わないこと ◦ 再代入しない変数にはletではなくconstを用いること

    • TypeScriptとESLintを併用する場合、 https://github.com/typescript-eslint/typescript-eslintを使います 115
  116. 周辺のツールチェイン:フォーマッター • ソースコードを機械的にフォーマットするためのツール • Prettier、Biome formatter、dprint、etc… • 下記のような不毛な議論に左右されなくなります ◦ 「文字列リテラルはシングルクォート

    or ダブルクォート」 ◦ 「セミコロンを文の末尾につけるべきか」 • ファイルの保存時やコミット時に自動的にフォーマットが走る設定にしておくと 便利です ◦ ファイルの保存時:VSCodeの設定 ◦ コミット時:Git Hookの設定、例えばHuskyを使うなど 116
  117. 周辺のツールチェイン:自動生成等 • I/Oやネットワーク通信など、外部との境界ではanyやunknownが発生しがち ◦ 実際、外部システムとインターフェースの認識がずれていた、または認識は あっていたけど実装がずれていた、みたいな障害がありがちです • 使用する通信プロトコルに合わせて、IDL(インターフェース記述言語)から TypeScriptの型を生成するツールも多数存在します ◦

    OpenAPI(Swagger), GraphQL, TypeORM, Prisma, etc… 117
  118. まとめ:この資料では以下のことを学びました • TSは ◦ Alt JS:JavaScriptにコンパイルされる ◦ (静的な)型が付いたJSのスーパーセット • 型注釈、型推論、number、string、boolean、symbol、null、undefined、bigint、リテラ

    ル型、オブジェクトリテラル型、配列型、タプル型、関数型、any、型エイリアス、 Class、Interface、Union、Optional、unknown、never、readonly、ジェネリクス、部分 型関係、型アサーション(as)、Intersection、Utility Types • 周辺には便利なツールがいっぱいある ◦ 上手に使いこなしましょう • 型は友達 118
  119. 参考文献 • TypeScriptの公式ドキュメント ◦ https://www.typescriptlang.org/docs/ • Pierce, Benjamin C. 型システム入門

    プログラミング言語と型の理論. 株式会社 オーム社, 2013. ◦ TAPLの略称でも参照している 119
  120. 型パズルでもやって遊んでいてください(Level 1) • 以下のようなIf型を定義してください 120 type CaseIf1 = If<true, 'a',

    'b'> // CaseIf1は'a'型 type CaseIf2 = If<false, 'a', 'b'> // CaseIf2は'b'型 出典:https://github.com/type-challenges/type-challenges/blob/main/questions/00268-easy-if/README.md
  121. 型パズルでもやって遊んでいてください(Level 2) • 以下のようなReverse型を定義してください 121 type CaseReverse1 = Reverse<['a', 'b']>

    // CaseReverse1は['b', 'a']型 type CaseReverse2 = Reverse<['a', 'b', 'c']> // CaseReverse2は['c', 'b', 'a']型 出典:https://github.com/type-challenges/type-challenges/blob/main/questions/03192-medium-reverse/README.md
  122. 型パズルでもやって遊んでいてください(Level 3) • 以下のような、0以上の整数の掛け算を行うMultiply型を定義してください • 入力される数値の範囲は自身で定義して構いません 122 type CaseMultiply1 =

    Multiply<2, 3> // CaseMultiply1は6型 type CaseMultiply2 = Multiply<4, 5> // CaseMultiply2は20型 type CaseMultiply3 = Multiply<6, 0> // CaseMultiply3は0型
  123. 型パズルでもやって遊んでいてください(Level 4) • 以下のような、指定した数までの素数に評価されるPrimes型を定義してください • 入力される数値の範囲は自身で定義して構いません 123 type CasePrimes1 =

    Primes<300> // CasePrimes1は[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]型
  124. 演習の回答例 124

  125. 演習1、問題1の回答例 125 const user: { firstName: string; lastName: string; age:

    number; favoriteFoods: string[]; hasProgrammingExperience: boolean; } = { firstName: "太郎", lastName: "山田", age: 24, favoriteFoods: ["寿司", "ラーメン", "カレー"], hasProgrammingExperience: true, };
  126. 演習1、問題2の回答例 126 const getSelfIntroduction = (user: { firstName: string; lastName:

    string; age: number; favoriteFoods: string[]; hasProgrammingExperience: boolean; }) => `私の名前は${user.lastName}${user.firstName}です。年齢は${ user.age }歳です。好きな食べ物は${user.favoriteFoods.join( "と" )}です。プログラミングの経験${ user.hasProgrammingExperience ? "があります" : "はありません" }`;
  127. 演習2、問題1の回答例 127 type Subject = | { kind: "name"; payload:

    string } | { kind: "favorite_food"; payload: string[] };
  128. 演習2、問題2の回答例 128 function printSelfIntroduction(subject: Subject) { switch (subject.kind) { case

    "name": console.log(`私の名前は${subject.payload}です`); return; case "favorite_food": console.log(`私の好きな食べ物は${subject.payload.join("と")}です`); return; default: const never: never = subject; never; } }
  129. 演習2、問題2の回答例2 129 function printSelfIntroduction(subject: Subject) { switch (subject.kind) { case

    "name": console.log(`私の名前は${subject.payload}です`); return; case "favorite_food": console.log(`私の好きな食べ物は${subject.payload.join("と")}です`); return; default: subject satisfies never; } }
  130. 演習2、問題3の回答例 130 const triple = <T>(arg: T): [T, T, T]

    => [arg, arg, arg];
  131. 演習3、問題1の回答例 • shouldExtractに渡せる関数を考えたい • ある関数をshouldExtractとして渡しても安全であるようにしたい • 「(S1) => S2 型の値」を「(bird:

    Bird) => boolean 型の値」として扱っても安 全であるようにしたい ◦ 「ある関数」を「(S1) => S2 型の値」とおいた • (S1) => S2 ⊆ (bird: Bird) => boolean としたい • Bird ⊆ S1 かつ S2 ⊆ boolean を満たせば良い ◦ S1:Bird、Animal、unknownなど ◦ S2:true、false、neverなど 131
  132. 演習3、問題1の回答例 132 const shouldExtract = (animal: Animal): boolean => animal.name

    === "スズメ"; const shouldExtract = (maybeBird: unknown): boolean => typeof maybeBird === "object" && maybeBird !== null && "name" in maybeBird ? maybeBird.name === "スズメ" : false; const shouldExtract = (_: Bird): true => true; const shouldExtract = (_: Bird): never => { while (true) {} };
  133. 演習3、問題3の回答例 133 (123 as unknown as string).charAt(0);


[8]ページ先頭

©2009-2025 Movatter.jp