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

TypeScript入門 2024

Avatar for Recruit Recruit
August 09, 2024

TypeScript入門 2024

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

Avatar for Recruit

Recruit

August 09, 2024
Tweet

More Decks by Recruit

Other Decks in Technology

Transcript

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

    • 普段:TypeScript、React、Next.jsでWebアプリケー ションを書いています • 趣味:型パズルなど • よろしくお願いします 2 フローリングの アイコンが私
  2. 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
  3. Alt JS:JavaScriptにコンパイルされる(3/3) • 古いブラウザでも動くようにコンパイルすることもできます • Downlevelingといいます ◦ 例:async / awaitはそのままではIEでは動作しないが、TSが頑張ってコー

    ドを変換してくれる • もちろん、「まったくDownlevelingしない」ということもできます ◦ 例:tsは型検査だけで、コンパイルはBabelなど別のツールに任せる 10
  4. ミニ演習1 1. TS Playgroundを開きます 2. TS Config -> Type Checking

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

    On Saveタブで以下を有効化 ◦ Enable format on save ◦ Prevent copy link on save 3. Command + sでフォーマットされる 14
  6. JSは動的な検査を行う言語(2/2) • または、次のように間違えたとします • こちらは実行してもエラーが出ません • そういう仕様なので 17 const msgString

    = 12345; console.log("現在のメッセージは" + msgString.length + "文字です"); 現在のメッセージはundefined文字です
  7. TSで書いてみる • すると、静的型検査で怒られました • ソースコードを実行する前に、静的解析によってエラーを発見できました 20 const msg: string =

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

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

    それよりは動的に検査してしまって、素直にエラー画面などを表示したほうが簡 単という場合もあります • やりたいことに応じて適切な手段を取れるようになりましょう 23
  10. 型注釈 • TSに「ここは〜〜型ですよ」と教える方法 • いろいろなところに書けます 25 const a: string =

    "hoge"; let b: string; var c: string; function greet(name: string): string { return "hello, " + name; } class MyClass { e: string = ""; }
  11. しかし、わかりやすさのために型注釈を書くこともある 27 *1:最近のエディタの中には、推論された型をinlay hintsによって表示してくれるものもある // 自明なので注釈は必須でない const num: number =

    123; // dataの型が自明でないので注釈がほしい*1 // 注釈があると、「長い処理」を書き間違ってしまったときにここでエラーが出る // 注釈を消して型推論に任せると、どこか別の場所でエラーが出てデバッグが大変になる かも const data = hogeArray.map(/* 長い処理 */).filter(/* 長い処理 */).reduce(/* さらにつづく...
  12. 基本的な型 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;
  13. リテラル型 • 特定の値しか代入できない型 • 数値リテラル型 / 文字列リテラル型 / 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;
  14. 配列型 • タプル型:Array型の一種 ◦ 各要素の型、順番、個数を正確に把握している型 32 const array1: number[] =

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

    // 引数がない時 () => string // 戻り値が無い時 (num: number, bool: boolean) => void
  16. 関数型(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}`; }
  17. any型(3/3) • anyあるある ◦ 既存のJSプロジェクトにTSを導入している。とりあえず導入だけ済ませ て、型は後から書きたい ◦ JSのライブラリをTSで使いたいが、型定義が不足している。自分で定義を 書くのは時間がかかるのでいったんanyにしておきたい ◦

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

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

    ]の中は引数によって変化する部分 ▪ 私の名前は[姓][名]です。年齢は[年齢]歳です。好きな食べ物は[〜〜 と〜〜と〜〜(〜〜には好きな食べ物配列の各要素が入る。要素数だけ 繰り返す)]です。プログラミングの経験[があります or はありません] • 問題3:getSelfIntroduction関数を実際に実行し、consoleに自己紹介文を出力 してみてください 40
  21. 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("コウモリ");
  22. 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; }
  23. 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";
  24. 型の絞り込み(1/2) • Union型は、if文やswitch文によって値をチェックしていくことで、より詳細な 型へ絞り込まれます 51 function fn(a?: number) { //

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

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

    any型の値はそこから何でもできた ▪ unknown型の値は、それを具体的な値に絞り込まないと何もできないの で安全 • never型 ◦ どのような値も代入できない ◦ 型の絞り込みの結果、到達不能な部分などで現れる 53
  27. 型の絞り込み:少し複雑な例 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; }
  28. readonly • 書き換え操作を型検査で抑止します ◦ ランタイムのJSからは書き換え可能 55 type Obj = {

    readonly prop: string; }; const obj: Obj = { prop: "hello", }; obj.prop = "goodbye"; // おこられる
  29. ジェネリクス(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; // ...
  30. ジェネリクス(4/4) • ジェネリクスを使うと、以下のようにできます 59 // 型変数Tで引数の型をキャプチャ const id = <T>(arg:

    T): T => arg; const result = id<number>(123); // resultはnumber型 // または型推論に任せて const result = id(123);
  31. 演習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; } }
  32. 演習2 • 引数 ◦ subject: 自己紹介の主題。kindとpayloadの2つのプロパティをもつ ▪ kind: 文字列。”name”または”favorite_food” •

    “name”:payloadには名前(文字列)が入る。 • “favorite_food”:payloadには好きな食べ物の名前の配列(文字列 の配列)が入る。 ▪ payload: kindによって変わる • 戻り値 ◦ なし 61
  33. 部分型関係(1/4) • サブタイプ関係、is-a関係とも • 例えば次のような型があります 64 type Animal = {

    name: string }; type Bird = { name: string; wings: "翼" }; type Dog = { name: string; forefoot: "前足" };
  34. 部分型関係(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}です`); // エラーにならない
  35. 部分型関係(3/4) • 逆はできません 66 type Animal = { name: string

    }; type Bird = { name: string; wings: "翼" }; const animal: Animal = { name: "スズメ" }; const bird: Bird = animal; // できない(型エラー) // もし↑を許すと、↓のようなコードが書けて、実行時エラーになる console.log(`この動物は${bird.wings}です`);
  36. 部分型関係(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
  37. 部分型関係の性質 • 反射的 ◦ 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
  38. オブジェクトの部分型関係(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); // エラーにならない
  39. オブジェクトの部分型関係(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}です`);
  40. 配列の部分型関係 • 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}です`); }
  41. 関数の部分型関係(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);
  42. 関数の部分型関係(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 の値として扱っても安全
  43. 関数の部分型関係(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}`);
  44. 関数の部分型関係(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 の値として扱っても安全
  45. 関数の部分型関係(5/8) Bird ⊆ Animalであるとき • (animal: Animal) => void の値を

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

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

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

    かつ S2 ⊆ T2 • ならば • (S1) => S2 ⊆ (T1) => T2 79 参考:TAPL 15.2節, p.144
  49. 部分型関係まとめ • 以下のようなエラーメッセージを見かけたら ◦ type '〜〜' is not assignable to

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

    ◦ 部分型関係はソースコード上に明示的に現れる ▪ 例:class Bird extends Animal { … • TSの型システムは構造的 ◦ 名前の無い型を書ける ▪ 例:{name: string} 型 ◦ 部分型関係は型の構造上に直接的に定義される 82 参考:TAPL 19.3節
  51. 発展:名前的と構造的(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(); } }
  52. 型アサーション(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型
  53. アップキャスト • source as Targetのとき、Source ⊆ Targetであること • 例:dog as

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

    Dog • 危険! ◦ Animalの値をDogとして扱う ◦ Dogにしかないプロパティにアクセスしたら実行時エラーになる • 先程の例のように、相当の理由と自信がある場合のみ使ってください 87 const myCanvas = document.getElementById("main_canvas") as HTMLCanvasElement;
  55. Intersection(1/2) • 「かつ」の意味 • Unionの逆 • 交差型とも 90 type A

    = number & unknown; // Aはnumber型 type B = number & never; // Bはnever型
  56. 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 }型
  57. 演習3 • 問題1: ◦ filterBirdsは、Birdの配列をフィルタリングする関数です。 ▪ 引数: • birds:Birdの配列 •

    shouldExtract:birdsの各要素を取り出すかを判定する関数 ▪ 戻り値:フィルタリングされたBirdの配列 ◦ filterBirdsに渡せるshouldExtractの実装を、次ページに示すサンプルコードの他 に2つ挙げてください ▪ 各実装でshouldExtractの型は異なる必要があります ▪ 実行時にJSのエラーになってはいけません ▪ filterの結果は変わっても良いです 92
  58. 演習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; };
  59. 演習3 • 問題2:以下はそれぞれどのような型でしょうか。確かめてみてください 94 type D = { prop: {

    a: number } } & { prop: { b: string } }; type E = number & 12345; type F = "Love" & "Peace";
  60. 発展: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; } // まだまだある
  61. モジュール • JSには複数のモジュールのスタイルがあります(歴史的事情) ◦ ES2015のモジュール ▪ 例:import hoge from “module-name”;

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

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

    • これにともなって、様々な課題が浮上 ◦ JSファイルを分割したいが、<script>タグによる読み込み回数は増やしたくない ◦ 別言語(AltJS)で開発したい ◦ ダウンロードサイズ削減のため、JSファイルを圧縮したい ◦ etc… • いろいろなツールが開発され、吟味され、混乱もあり、いろいろあって…… 110
  64. 周辺のツールチェイン:TypeScript自体の便利機能 • TypeScript本体に付属しているtscというツール自体にも便利機能があります ◦ --noEmit : JSの生成を行なわずに、型検査のみを実行するオプション. CI等 に組み込むと便利 ◦

    --noUnusedLocals、--noUnusedParameters: 利用されていない変数を検知 してエラーにするオプション. 冗長なコードの削減になる • その他にも、tscには様々なオプションが用意されています。公式ドキュメントを 見てみるとよいでしょう 114
  65. 周辺のツールチェイン:フォーマッター • ソースコードを機械的にフォーマットするためのツール • Prettier、Biome formatter、dprint、etc… • 下記のような不毛な議論に左右されなくなります ◦ 「文字列リテラルはシングルクォート

    or ダブルクォート」 ◦ 「セミコロンを文の末尾につけるべきか」 • ファイルの保存時やコミット時に自動的にフォーマットが走る設定にしておくと 便利です ◦ ファイルの保存時:VSCodeの設定 ◦ コミット時:Git Hookの設定、例えばHuskyを使うなど 116
  66. まとめ:この資料では以下のことを学びました • 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
  67. 参考文献 • TypeScriptの公式ドキュメント ◦ https://www.typescriptlang.org/docs/ • Pierce, Benjamin C. 型システム入門

    プログラミング言語と型の理論. 株式会社 オーム社, 2013. ◦ TAPLの略称でも参照している 119
  68. 型パズルでもやって遊んでいてください(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
  69. 型パズルでもやって遊んでいてください(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
  70. 型パズルでもやって遊んでいてください(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]型
  71. 演習1、問題1の回答例 125 const user: { firstName: string; lastName: string; age:

    number; favoriteFoods: string[]; hasProgrammingExperience: boolean; } = { firstName: "太郎", lastName: "山田", age: 24, favoriteFoods: ["寿司", "ラーメン", "カレー"], hasProgrammingExperience: true, };
  72. 演習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 ? "があります" : "はありません" }`;
  73. 演習2、問題1の回答例 127 type Subject = | { kind: "name"; payload:

    string } | { kind: "favorite_food"; payload: string[] };
  74. 演習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; } }
  75. 演習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; } }
  76. 演習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
  77. 演習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) {} };