type-challenges - easy
この辺type-challengeやる前に読んだ・見た
ちなみにeasyに関しては2週目
TypeScript Docs
Utility Types
Conditional Types
Mapped Types
Frontend Masters
- Intermediate TypeScript
- Conditional Types
- Mapped Types
Pick
できた
type MyPick<T, K extends keyof T> = { [P in K]: P }
keyof
The keyof operator takes an object type and produces a string or numeric literal union of its keys. The following type P is the same type as “x” | “y”:
keyof
は、string または numeric literal をユニオン型で返す。
e.g.
type User = { id: number; name: string; age: number }
type UserKeys = keyof User; // 'id' | 'name' | 'age'
Mapped Types
A mapped type is a generic type which uses a union of PropertyKeys (frequently created via a keyof) to iterate through keys to create a type:
型のプロパティのキー(s)をユニオン型で返し、ループして型を作成する。
syntax
Property in keyof Type
-
keyof Type
- Typeのプロパティをユニオン型で返す
-
Property in UnionType
- Propertyにユニオン型の要素が順に入ってくる
e.g.
type User = { id: number; name: string; age: number }
// User型の値の方をnumberにコンバートする
type ValToNum<Type> = {
[P in keyof Type]: number
}
Readonly
できた
type MyReadonly<T> = {
readonly [K in keyof T]: T[K]
}
readonly
There are two additional modifiers which can be applied during mapping: readonly and ? which affect mutability and optionality respectively.
readonly: mutability
?: optionality
You can remove or add these modifiers by prefixing with - or +. If you don’t add a prefix, then + is assumed.
+
, -
でmodifiers (readonly, ?) の付け外しが可能。
syntax
それぞれのmodifiersのprefixとしてつける。
e.g.
type User = { id: string; name: string; age?: number }
type OptionalProperties<T> = {
[K in keyof T]+?: T[K]
}
type RequiredProperties<T> = {
[K in keyof T]-?: T[K]
}
const optionalUser: OptionalProperties<User> = {
name: 'john'
}
// Property 'age' is missing in type '{ id: string; name: string; }' but required in type 'RequiredProperties<User>'.
const requiredUser: RequiredProperties<User> = {
id: 'randomstring',
name: 'john'
}
Tuple to Object
できなかった
type TupleToObject<T extends readonly any[]> = {
[K in T[number]]: K
}
T[number]って発想がなかった。
Indexed Access Types
Another example of indexing with an arbitrary type is using number to get the type of an array’s elements. We can combine this with typeof to conveniently capture the element type of an array literal:
任意の型をインデックスとしてnumberを使うことで、配列の型を返す。
typeof
との組み合わせで、配列の要素を取得する
- arbitrary: 任意の
typeof
JavaScriptのtypeofと同じで、与えられた値の型を返す。
as const
ってなんだっけ?
array literals become readonly tuples
配列に対してas const
つけると readonlyなタプルになる
typeof
で型をチェックしてみると...
e.g.
const tuple = ['javascript', 'typescript', 'python', 'java'] as const
// type TypeOfTuple = readonly ["javascript", "typescript", "python", "java"]
type TypeOfTuple = typeof tuple
tuple to union
- typeofでtupleの型を返す
- indexed access typesの [number]を使って、1をユニオン型へ
const tuple = ['javascript', 'typescript', 'python', 'java'] as const
// type TypeOfTuple = readonly ["javascript", "typescript", "python", "java"]
type TypeOfTuple = typeof tuple
// type Languages = "javascript" | "typescript" | "python" | "java"
type Languages = TypeOfTuple[number]
ユニオン型にすれば
[K in Union]
が使えるので...
再掲になるががこういう回答ができる
type TupleToObject<T extends readonly any[]> = {
[K in T[number]]: K
}
// 用意されてるテスト
type cases = [
Expect<Equal<TupleToObject<typeof tuple>, { tesla: 'tesla'; 'model 3': 'model 3'; 'model X': 'model X'; 'model Y': 'model Y'}>>,
]
-
readonly any[]
は tupleの型を表現してる -
[K in T[number]
T[number]はユニオン型を返すので、それぞれの要素が入ってくる
First of Array
できた
type First<T extends any[]> = T extends [] ? never : T[0]
Conditional Types
When the type on the left of the extends is assignable to the one on the right, then you’ll get the type in the first branch (the “true” branch); otherwise you’ll get the type in the latter branch (the “false” branch).
A extends B ? true : false
型Aが型Bにアサイン可能かどうかをチェックする
例えば...
type answer1 = 64 extends number ? true : false
type answer2 = number extends 64 ? true : false
answer1 64
というnumeric literalはnumber
にアサイン可能
answer2 numberは64
にはアサインできない
アサイン: 代入
const value1: 64 = 6
// Type '6' is not assignable to type '64'
const value2: number = 64
// assignable
extends
keyword 条件式で使われるけど、equality checkではない。
個人的には ===
とか !==
とか使えるともっと直感的なんだけどな。。
型システムだから型がassignableかどうかで判断するのがメイクセンスな気もするけど。。
e.g.
type answer1 = 64 extends number ? true : false
// true
type answer2 = number extends 64 ? true : false
// false
type answer3 = string[] extends any ? true : false
// true
type answer4 = string[] extends any[] ? true : false
// true
3回目やった時の解法
別解
type First<T extends any[]> = T extends [first: infer First, ...rest: any] ? First : never
Length of Tuple
できなかった。
readonlyつけるの忘れてた。
type Length<T extends readonly any[]> = T['length']
const tuple = ['javascript', 'typescript', 'python', 'java'] as const
// type TypeOfTuple = readonly ["javascript", "typescript", "python", "java"]
type TypeOfTuple = typeof tuple
tupleの型は readonly string[]
Exclude
できなかった
1回目できたの謎
type MyExclude<T, U> = T extends U ? never : T;
T
、U
ともにユニオン型が入る。
これがしっくりきてなかった。ユニオン型ならin
使って反復処理した上で条件分岐って考えた。
この課題を解くにあたり重要になるポイントは Conditional Types が分配法則(Distributive) に従うと
いう点です。Conditional Types の条件部において T extends U の T が ユニオン型である場合 T に対して反復処理を行い各要素に条件を適用します。
なるほど。明示的に反復処理をさせなくてもやってくれるんだ。
Distributive Conditional Types
When conditional types act on a generic type, they become distributive when given a union type.
条件部分で、ジェネリックな型としてユニオン型が渡された時、分配法則になる。
T extends any
で、T
がstring | number
の場合 それぞれの要素に対して処理が行われる
e.g.
type ToArray<T> = T extends any ? T[] : never
type StrArrOrNumArr = ToArray<string | number>
// type StrArrOrNumArr = string[] | number[]
これ1回目、分配法則とか気にせず T extends U ? never : T
したらコンパイルエラー出なかったから、お、できたと思って、スルーしてたなきっと。
Awaited
できなかった。
infer + 再帰の問題だなとは思った。
type MyAwaited<T> = T extends Promise<infer U> ? MyAwaited<U> : T
If
できた
type If<C, T, F> = C extends true ? T : F
Concat
できた
type Concat<T extends any[], U extends any[]> = [...T, ...U]
spread operatorで配列の中身を展開可能にするために、extends any[]
で型を配列として受け取る必要があった。
Includes
できなかった
type Includes<T extends readonly any[], U> = T extends [infer R, ...infer Rest]
? Equal<R, U> extends true
? true
: Includes<Rest, U>
: false;
最初こう書いた
type Includes<T extends readonly any[], U> = U extends T[number] ? true : false;
単純にUがT[number] に代入可能ならtrue
これだと以下のようなケースに対応できない
Expect<Equal<Includes<[boolean, 2, 3, 5, 6, 7], false>, false>>,
false extends boolean
は代入可能なのでtrue
として評価される
Recursive Conditioanl Types
Keep in mind that while these recursive types are powerful, but they should be used responsibly and sparingly.
節度を持って使うべき
But apart from being computationally intensive, these types can hit an internal recursion depth limit on sufficiently-complex inputs. When that recursion limit is hit, that results in a compile-time error.
計算量が多いということもあるが、内部的に再帰の深さ制限に引っかかり、コンパイルエラーを引き起こす可能性がある。
ただ、type-challengeにおいて、反復処理が必要になりそうなときはこれが使えるというのが頭にあると良さそう
反復処理をしたいときは再帰的に書けるというのを念頭に置いておくとアプローチしやすいかもしれない。
おー、type-challengeが用意しているutil型のEqualを使ってる回答があった。他のcloseされてるPRでも、Equalの部分を自前で書いてる人が多かったけど、のちに追加されたんかな?
型の先頭から順々(再帰的)に比較していってマッチするものが来れば true
を返す。最終的に残り要素が一つになった時点で T extends [infer R, ...infer Rest]
でTが拡張可能ではなくなるのでfalse
を返す。
再掲
type Includes<T extends readonly any[], U> = T extends [infer R, ...infer Rest]
? Equal<R, U> extends true
? true
: Includes<Rest, U>
: false;
Push
できた
type Push<T extends any[], U> = [...T, U]
Unshift
できた
type Unshift<T extends any[], U> = [U, ...T]