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

type-challenges勉強会 - フロントエンドエンジニア育成PJ

type-challenges勉強会 - フロントエンドエンジニア育成PJ

More Decks by レバレジーズTechアカウント

Other Decks in Programming

Transcript

  1. type-challenges

    View full-size slide

  2. TypeScriptの型についての問題集のようなもの
    型に関する問題とそのテストケースが提示され
    それを満たす実装を書いてコンパイルエラーをなくしていくという流れで行なう
    こちらからチャレンジできます!!
    type-challengesとは?

    View full-size slide

  3. TSの型表現の向上が図れる!
    - 依存ライブラリの型の読解時
    - 汎用的な処理に汎用的な型表現を搭載する時
    - 依存ライブラリの型をラップしてより厳密な型を作りたい時
    などなど、型表現の方法を知っていて損はない!
    type-challengesやるとどうなるの?

    View full-size slide

  4. 1. type-chellengesリポジトリに組み込まれている
    ユーティリティタイプが提示される。
    type Result = Unshift<[1, 2], 0> // [0, 1, 2,]
    2. 上記の例だと、配列Array.Unshiftの用に配列型と要素が渡された際に、
    配列の頭に要素を追加する関数 Unshift を定義せよというもの。
    3. Unshiftを実装する。
    type-challengeの流れ

    View full-size slide

  5. 可変長タプル型

    View full-size slide

  6. 💭 可変長タプル型 is 何
    タプル型の亜種。タプル型の中に任意個の要素を詰め込むことができ、...配列型 でされる。
    例えば、下記の用にnumberとstringの可変長タプル型でタプル型を表す。
    type NumberAndStrings = [number, …string[]]
    // これらはOK
    const arr1: NumberAndStrings = [25, "foo", "baa", "hoge"]
    const arr2: NumberAndStrings = [25]
    // これらはNG
    const arr3: NumberAndStrings = ["foo", "baa"]
    const arr4: NumberAndStrings = [25, 26, 27]
    const arr5: NumberAndStrings = []

    View full-size slide

  7. また、もう一つの使い方としてスプレッド構文の様な作用ももっており、
    タプル型や配列型を別のタプル型に埋め込む事もできる。
    type NSN = [number, string, number]
    type SNSNS = [string, ...NSN, string]
    上記SNSNS型はNSN型の中身が展開され、
    type SNSNS = [string, number, string, number, string]
    と同様の型を示す。
    💭 可変長タプル型 is 何

    View full-size slide

  8. 🔥 Unshift
    Array.unshiftの型バージョンを実装します。
    例えば:
    type Result = Unshift<[1, 2], 0> // [0, 1, 2]

    View full-size slide

  9. 💡 解答例&解説
    type Unshift = [U, ...T]
    type Result1 = Unshift<[1, 2], 0> // [0, 1, 2]
    type Result2 = Unshift<[1], true>; // [true, 1]
    type Result3 = Unshift<["foo", "hoge"], "baa">; // ["baa", "foo", "hoge"]

    View full-size slide

  10. 💡 解答例&解説
    type Unshift = [U, ...T]
    type Result1 = Unshift<[1, 2], 0> // [0, 1, 2]
    type Result2 = Unshift<[1], true>; // [true, 1]
    type Result3 = Unshift<["foo", "hoge"], "baa">; // ["baa", "foo", "hoge"]
    Unshiftに渡される第1要素は配列であることが決まっているため
    要素を厭わないany型の配列型という制約を課す

    View full-size slide

  11. 💡 解答例&解説
    type Unshift = [U, ...T]
    type Result1 = Unshift<[1, 2], 0> // [0, 1, 2]
    type Result2 = Unshift<[1], true>; // [true, 1]
    type Result3 = Unshift<["foo", "hoge"], "baa">; // ["baa", "foo", "hoge"]
    Unshiftに渡される第二要素はそのまま配列のはじめにいれ、
    第一要素は可変長タプル型で展開して配列の後ろの要素とする。

    View full-size slide

  12. JavaScript のArray.concat関数を型システムに実装します。この型は 2 つの引
    数を受け取り、受け取ったイテレータの要素を順に含む新しい配列を返します。
    例えば:
    type Result = Concat<[1], [2]>; // expected to be [1, 2]
    🔥 Concat

    View full-size slide

  13. type Concat = [...T, ...U];
    type Result1 = Concat<[1], [2]>; // [1, 2]
    type Result2 = Concat<["a", "b"], ["c", "d"]>; // ["a", "b", "c", "d"]
    type Result3 = Concat<[true, 1], ["a", [2]]>; // [true, 1, "a", [2]]
    💡 解答例&解説

    View full-size slide

  14. type Concat = [...T, ...U];
    type Result1 = Concat<[1], [2]>; // [1, 2]
    type Result2 = Concat<["a", "b"], ["c", "d"]>; // ["a", "b", "c", "d"]
    type Result3 = Concat<[true, 1], ["a", [2]]>; // [true, 1, "a", [2]]
    先程の例同様、Concatに渡す第一要素、第二要素はany型の配列型に制限し
    可変長タプル型の展開を行った上で配列の要素として詰め直す。
    💡 解答例&解説

    View full-size slide

  15. 配列の型操作を柔軟に行える。
    tail関数は与えられた配列の最初の要素以外を返す関数。
    const tail = (tuple: readonly [S, ...T]): T => {
    const [, ...rest] = tuple;
    return rest;
    };
    // const t2: [number, string, number]
    const t2 = tail(["foo", 1, "bar", 2]);
    // const t2: [1, “bar”, 2]
    const t3 = tail(["foo", 1, "bar", 2] as const);
    ✨ 可変長タプル型 はこういうときに使える

    View full-size slide

  16. Indexed Access Types

    View full-size slide

  17. 💭 Indexed Access Types is 何
    基本形: T[K]
    オブジェクトのプロパティアクセスのように、オブジェクトの持つ特定の型を取
    得したい時に使う。
    type Foo = {a: number; b: string}
    type Bar = Foo['a'] // Bar = number

    View full-size slide

  18. タプルTを受け取り、そのタプルの長さを返す型Lengthを実装します。
    例えば:
    type tesla = ['tesla', 'model 3', 'model X', 'model Y']
    type spaceX = ['FALCON 9', 'FALCON HEAVY', 'DRAGON', 'STARSHIP', 'HUMAN SPACEFLIGHT']
    type teslaLength = Length // expected 4
    type spaceXLength = Length // expected 5
    🔥 Length of Tuple

    View full-size slide

  19. type Length = T['length']
    Array.length プロパティにアクセスすると配列の要素の数を取得できる。
    同じように、型変数に対して length プロパティにアクセスすれば、タプルの要
    素数を取得することができる。
    💡 解答例&解説

    View full-size slide

  20. Mapped Types

    View full-size slide

  21. 💭 Mapped Types is 何
    基本形: { [P in K]: T }
    ユニオン型のそれぞれをキーとして、型を定義したい時に使う。
    type Foo = 'a' | 'b' | 'c'
    type Bar = { [P in Foo]: string }
    // Bar = {a: string; b: string; c: string}

    View full-size slide

  22. 💭 keyof is 何
    基本形: keyof T
    あるオブジェクトからキーを取得したい時に使う。ユニオン型で取得できる。
    type Foo = {a: number; b: string}
    type Bar = keyof Foo // Bar = 'a' | 'b'

    View full-size slide

  23. 🔥 Pick
    組み込みの型ユーティリティPickを使用せず、TからKのプロパティを抽出する型
    を実装します。
    例えば:
    interface Todo {
    title: string
    description: string
    completed: boolean
    }
    type TodoPreview = MyPick
    const todo: TodoPreview = {
    title: 'Clean room',
    completed: false,
    }

    View full-size slide

  24. 💡 解答例&解説
    type MyPick = {
    [P in K]: T[P]
    }
    K extends keyof T で、第二引数が第一引数のキーのみを受け取るよう制約を設ける。
    [P in K] (Mapped Types)で、第二引数のユニオン型(K)を順に取り出す。
    T[P] (Indexed Access Types)で、キーに対応するTの型を取り出す。

    View full-size slide

  25. 💡 解答例&解説(keyof)
    type Foo = { a: number; b: string; c: string }
    type Bar = MyPick // Bar = { a: number; c: string }
    type MyPick = {
    [P in K]: T[P]
    }
    "a" | "c" extends keyof Foo

    "a" | "c" extends "a" | "b" | "c"

    View full-size slide

  26. 💡 解答例&解説(Mapped Types)
    type Foo = { a: number; b: string; c: string }
    type Bar = MyPick // Bar = { a: number; c: string }
    type MyPick = {
    [P in K]: T[P]
    }
    [P in "a" | "c"]: Foo[P]

    { a: Foo["a"]; c: Foo["c"] }

    View full-size slide

  27. 💡 解答例&解説(Indexed Access Types)
    type Foo = { a: number; b: string; c: string }
    type Bar = MyPick // Bar = { a: number; c: string }
    type MyPick = {
    [P in K]: T[P]
    }
    { a: Foo["a"]; c: Foo["c"] }

    { a: number; c: string }

    View full-size slide

  28. 💡 解答例&解説 まとめ
    type MyPick = {
    [P in K]: T[P]
    }
    K extends keyof T で、第二引数が第一引数のキーのみを受け取るよう制約を設ける。
    [P in K] (Mapped Types)で、第二引数のユニオン型(K)を順に取り出す。
    T[P] (Indexed Access Types)で、キーに対応するTの型を取り出す。

    View full-size slide

  29. 🔥 Readonly
    組み込みの型ユーティリティReadonlyを使用せず、T のすべてのプロパティを読み取り専
    用にする型を実装します。実装された型のプロパティは再割り当てできません。
    例えば:
    interface Todo {
    title: string
    description: string
    }
    const todo: MyReadonly = {
    title: "Hey",
    description: "foobar"
    }
    todo.title = "Hello" // Error: cannot reassign a readonly property
    todo.description = "barFoo" // Error: cannot reassign a readonly property

    View full-size slide

  30. 💡 解答例&解説
    // 解答例
    type MyReadonly = {
    readonly [P in keyof T]: T[P]
    }
    👉 readonly はプロパティを読み取り専用にできる。
    👉 [P in T]: K ユニオン型Tの各構成要素Pそれぞれが、型Kを持つオブジェクトの型
    例えばK = P1 | P2 | P3の場合、{ P1: K, P2: K, P3: K } // Mapped Type
    👉 [P in keyof T]: T[P] 上述のmapped typesにおいて、結果がTの構造を維持する。
    Tがオブジェクトであり、オブジェクトの結果を返したい時などに使う。

    View full-size slide

  31. Conditional Types

    View full-size slide

  32. 以下の構文を持つ型。
    「XがYの部分型ならばS、そうでなければT」という意味になる。
    X extends Y ? S : T
    💭 Conditional Types is 何

    View full-size slide

  33. 組み込みの型ユーティリティExclude を使用せず、Uに割り当て可能な型
    をTから除外する型を実装します。
    例えば:
    type Result = MyExclude<'a' | 'b' | 'c', 'a'> // 'b' | 'c'
    🔥 Exclude

    View full-size slide

  34. // 解答例
    type MyExclude = T extends U ? never : T
    👉 X extends Y ? S : T Conditional Typeを利用する
    👉(A extends Y ? Sa : Ta) | (B extends Y ? Sb : Tb)
    Xの中身がA | Bである場合、X extends Y ? S : Tは上記の型になる。//union distributon
    👉('a' | 'b' | 'c') extends 'a' ? never : ('a' | 'b' | 'c') 
    今回の問題だとMyExcludeの中身は上記となる
    👉 ('a' extends 'a' ? never : 'a') | ('b' extends 'a' ? never : 'b') | ('c' extends 'a' ?
    never : 'c')
    union distributonにより以下のようになり、'b' | 'c'の型ができる。
    💡 解答例&解説

    View full-size slide

  35. ✨ Conditional Types はこういうときに使える
    ● 指定した特定の型のみを抽出する
    ○ T extends U ? T : never で型Uを満たさない型Tは除外することができる
    ● 指定した特定の型のみを除去する
    ○ T extends U ? never : T は上記と逆に第2引数で指定した型を第1引数のUnion型から除去す
    ることができる
    ● 特定のプロパティを持つオブジェクトのみを取り出す
    ○ 下記から{barks: true}を持つ型を取り出す。type ExtractDogish = T extends {
    barks: true } ? T : never;でDog | Wolfを取り出すことができる
    ■ type Cat = { meows: true };
    ■ type Dog = { barks: true };
    ■ type Cheetah = { meows: true; fast: true };
    ■ type Wolf = { barks: true; howls: true };

    View full-size slide

  36. Inferring Within
    Conditional Types

    View full-size slide

  37. Conditional Types内で型推論が利用できる。
    // Ex.配列の要素の型を抜き出す(配列ではない場合は渡された型をそのまま返す)
    type Flatten = Type extends (infer Item)[] ? Item : Type
     👉 Item には推論された任意の型が入る
     👉 infer で推論した型は Conditional Types 内で利用できる
    💭 Inferring Within Conditional Types is 何
    参考:Inferring Within Conditional Types

    View full-size slide

  38. Promise ライクな型が内包する型をどのように取得すればよいでしょうか。
    例えば:Promiseという型がある場合、どのようにして
    ExampleType を取得すればよいでしょうか。
    type ExampleType = Promise
    type Result = MyAwaited // string
    🔥 Awaited

    View full-size slide

  39. type MyAwaited> = T extends Promise ? U : T
    type ExampleType = Promise
    type Result = MyAwaited // string
    👉 U には推論された任意の型が入る
    👉 infer で推論した型は Conditional Types 内で利用できる
    👉 ただ、この場合だとPromiseがネストしている場合に対応できない。。
     type Result = MyAwaited>> // Promise
    💡 解答例

    View full-size slide

  40. type MyAwaited> = T extends Promise
    ? U extends Promise // Promiseにラップされた中身が Promiseかどうかチェック
    ? MyAwaited // 再帰的に型を通す
    : U
    : T
    type Result1 = MyAwaited>> // string
    type Result2 = MyAwaited>>> // string
    👉 Inferring Within Conditional Typesを利用すれば再帰的な型を定義できる
    💡 解答例(改)

    View full-size slide

  41. めちゃくちゃ厳密にPromise型かどうかチェックしてた
    type Awaited =
    T extends null | undefined ? T : // nullもしくはundefinedではないかチェック
    T extends object & { then(onfulfilled: infer F, ...args: infer _): any } ? // 関数thenを持つかどうかチェック
    F extends ((value: infer V, ...args: infer _) => any) ? // 関数 then に渡すコールバック関数の第一引数を見る
    Awaited : // 再起的に型を通す
    never : // 関数 then を持つが呼び出し可能ではない
    T; // オブジェクトではない or 関数 then を持たない
    💡 ちなみにTypeScriptのユーティリティ型では。。
    参考:TypeScript/src/lib/es5.d.ts

    View full-size slide

  42. 🔥 Parameters
    組み込みの型ユーティリティParametersを使用せず、Tからタプル型を構築
    する型を実装します。
    例えば:
    const foo = (arg1: string, arg2: number): void => {}
    type FunctionParamsType = MyParameters // [arg1: string, arg2: number]

    View full-size slide

  43. type MyParameters any> =
    T extends (...args: infer P) => any ? P : never;
    const foo = (arg1: string, arg2: number): void => {}
    type FunctionParamsType = MyParameters // [arg1: string, arg2: number]
    👉 P には推論された任意の可変長タプル型が入る
    👉 引数リストを可変長タプル型として定義することでどのような引数の個数
      でも対応できる
    💡 解答例 ※これはTypeScriptのユーティリティ型と一緒

    View full-size slide

  44. 👉 型でラップされた中身の型を取り出す
      type MyAwaited> = T extends Promise ? U : T
    👉 オブジェクト型の特定のプロパティの型を取り出す
      type Value = T extends Record ? U : never
      type Man = {
      name: string;
      age: number;
      }
      type Name = Value; // string
      type Age = Value; // number
    ✨ Inferring Within Conditional Types はこういうときに使える

    View full-size slide