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

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

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

Tech Leverages

July 31, 2023
Tweet

More Decks by Tech Leverages

Other Decks in Programming

Transcript

  1. 1. type-chellengesリポジトリに組み込まれている ユーティリティタイプが提示される。 type Result = Unshift<[1, 2], 0> //

    [0, 1, 2,] 2. 上記の例だと、配列Array.Unshiftの用に配列型と要素が渡された際に、 配列の頭に要素を追加する関数 Unshift を定義せよというもの。 3. Unshiftを実装する。 type-challengeの流れ
  2. 💭 可変長タプル型 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 = []
  3. また、もう一つの使い方としてスプレッド構文の様な作用ももっており、 タプル型や配列型を別のタプル型に埋め込む事もできる。 type NSN = [number, string, number] type SNSNS

    = [string, ...NSN, string] 上記SNSNS型はNSN型の中身が展開され、 type SNSNS = [string, number, string, number, string] と同様の型を示す。 💭 可変長タプル型 is 何
  4. 💡 解答例&解説 type Unshift<T extends any[], U> = [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"]
  5. 💡 解答例&解説 type Unshift<T extends any[], U> = [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型の配列型という制約を課す
  6. 💡 解答例&解説 type Unshift<T extends any[], U> = [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に渡される第二要素はそのまま配列のはじめにいれ、 第一要素は可変長タプル型で展開して配列の後ろの要素とする。
  7. type Concat<T extends any[], U extends any[]> = [...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]] 💡 解答例&解説
  8. type Concat<T extends any[], U extends any[]> = [...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型の配列型に制限し 可変長タプル型の展開を行った上で配列の要素として詰め直す。 💡 解答例&解説
  9. 配列の型操作を柔軟に行える。 tail関数は与えられた配列の最初の要素以外を返す関数。 const tail = <S, T extends readonly any[]>(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); ✨ 可変長タプル型 はこういうときに使える
  10. タプルTを受け取り、そのタプルの長さを返す型Length<T>を実装します。 例えば: type tesla = ['tesla', 'model 3', 'model X',

    'model Y'] type spaceX = ['FALCON 9', 'FALCON HEAVY', 'DRAGON', 'STARSHIP', 'HUMAN SPACEFLIGHT'] type teslaLength = Length<tesla> // expected 4 type spaceXLength = Length<spaceX> // expected 5 🔥 Length of Tuple
  11. type Length<T extends readonly any[]> = T['length'] Array.length プロパティにアクセスすると配列の要素の数を取得できる。 同じように、型変数に対して

    length プロパティにアクセスすれば、タプルの要 素数を取得することができる。 💡 解答例&解説
  12. 💭 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}
  13. 🔥 Pick 組み込みの型ユーティリティPick<T, K>を使用せず、TからKのプロパティを抽出する型 を実装します。 例えば: interface Todo { title:

    string description: string completed: boolean } type TodoPreview = MyPick<Todo, 'title' | 'completed'> const todo: TodoPreview = { title: 'Clean room', completed: false, }
  14. 💡 解答例&解説 type MyPick<T, K extends keyof T> = {

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

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

    string; c: string } type Bar = MyPick<Foo, "a" | "c"> // Bar = { a: number; c: string } type MyPick<T, K extends keyof T> = { [P in K]: T[P] } [P in "a" | "c"]: Foo[P] ↓ { a: Foo["a"]; c: Foo["c"] }
  17. 💡 解答例&解説(Indexed Access Types) type Foo = { a: number;

    b: string; c: string } type Bar = MyPick<Foo, "a" | "c"> // Bar = { a: number; c: string } type MyPick<T, K extends keyof T> = { [P in K]: T[P] } { a: Foo["a"]; c: Foo["c"] } ↓ { a: number; c: string }
  18. 💡 解答例&解説 まとめ type MyPick<T, K extends keyof T> =

    { [P in K]: T[P] } K extends keyof T で、第二引数が第一引数のキーのみを受け取るよう制約を設ける。 [P in K] (Mapped Types)で、第二引数のユニオン型(K)を順に取り出す。 T[P] (Indexed Access Types)で、キーに対応するTの型を取り出す。
  19. 🔥 Readonly 組み込みの型ユーティリティReadonly<T>を使用せず、T のすべてのプロパティを読み取り専 用にする型を実装します。実装された型のプロパティは再割り当てできません。 例えば: interface Todo { title:

    string description: string } const todo: MyReadonly<Todo> = { title: "Hey", description: "foobar" } todo.title = "Hello" // Error: cannot reassign a readonly property todo.description = "barFoo" // Error: cannot reassign a readonly property
  20. 💡 解答例&解説 // 解答例 type MyReadonly<T> = { 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がオブジェクトであり、オブジェクトの結果を返したい時などに使う。
  21. // 解答例 type MyExclude<T, U> = 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'の型ができる。 💡 解答例&解説
  22. ✨ Conditional Types はこういうときに使える • 指定した特定の型のみを抽出する ◦ T extends U

    ? T : never で型Uを満たさない型Tは除外することができる • 指定した特定の型のみを除去する ◦ T extends U ? never : T は上記と逆に第2引数で指定した型を第1引数のUnion型から除去す ることができる • 特定のプロパティを持つオブジェクトのみを取り出す ◦ 下記から{barks: true}を持つ型を取り出す。type ExtractDogish<T> = 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 };
  23. Conditional Types内で型推論が利用できる。 // Ex.配列の要素の型を抜き出す(配列ではない場合は渡された型をそのまま返す) type Flatten<Type> = Type extends (infer

    Item)[] ? Item : Type  👉 Item には推論された任意の型が入る  👉 infer で推論した型は Conditional Types 内で利用できる 💭 Inferring Within Conditional Types is 何 参考:Inferring Within Conditional Types
  24. type MyAwaited<T extends Promise<any>> = T extends Promise<infer U> ?

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

    U extends Promise<any> // Promiseにラップされた中身が Promiseかどうかチェック ? MyAwaited<U> // 再帰的に型を通す : U : T type Result1 = MyAwaited<Promise<Promise<string>>> // string type Result2 = MyAwaited<Promise<Promise<Promise<string>>>> // string 👉 Inferring Within Conditional Typesを利用すれば再帰的な型を定義できる 💡 解答例(改)
  26. めちゃくちゃ厳密にPromise型かどうかチェックしてた type Awaited<T> = 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<V> : // 再起的に型を通す never : // 関数 then を持つが呼び出し可能ではない T; // オブジェクトではない or 関数 then を持たない 💡 ちなみにTypeScriptのユーティリティ型では。。 参考:TypeScript/src/lib/es5.d.ts
  27. type MyParameters<T extends (...args: any) => any> = T extends

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

    Promise<infer U> ? U : T 👉 オブジェクト型の特定のプロパティの型を取り出す   type Value<T, K extends keyof T> = T extends Record<K, infer U> ? U : never   type Man = {   name: string;   age: number;   }   type Name = Value<Man, 'name'>; // string   type Age = Value<Man, 'age'>; // number ✨ Inferring Within Conditional Types はこういうときに使える