Slide 1

Slide 1 text

type-challenges

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

可変長タプル型

Slide 6

Slide 6 text

💭 可変長タプル型 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 = []

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

💡 解答例&解説 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"]

Slide 10

Slide 10 text

💡 解答例&解説 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型の配列型という制約を課す

Slide 11

Slide 11 text

💡 解答例&解説 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に渡される第二要素はそのまま配列のはじめにいれ、 第一要素は可変長タプル型で展開して配列の後ろの要素とする。

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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]] 💡 解答例&解説

Slide 14

Slide 14 text

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型の配列型に制限し 可変長タプル型の展開を行った上で配列の要素として詰め直す。 💡 解答例&解説

Slide 15

Slide 15 text

配列の型操作を柔軟に行える。 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); ✨ 可変長タプル型 はこういうときに使える

Slide 16

Slide 16 text

Indexed Access Types

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

タプル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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

Mapped Types

Slide 21

Slide 21 text

💭 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}

Slide 22

Slide 22 text

keyof

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

💡 解答例&解説(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"

Slide 27

Slide 27 text

💡 解答例&解説(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"] }

Slide 28

Slide 28 text

💡 解答例&解説(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 }

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

🔥 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

Slide 31

Slide 31 text

💡 解答例&解説 // 解答例 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がオブジェクトであり、オブジェクトの結果を返したい時などに使う。

Slide 32

Slide 32 text

Conditional Types

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

// 解答例 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'の型ができる。 💡 解答例&解説

Slide 36

Slide 36 text

✨ 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 };

Slide 37

Slide 37 text

Inferring Within Conditional Types

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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を利用すれば再帰的な型を定義できる 💡 解答例(改)

Slide 42

Slide 42 text

めちゃくちゃ厳密に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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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のユーティリティ型と一緒

Slide 45

Slide 45 text

👉 型でラップされた中身の型を取り出す   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 はこういうときに使える