type-challengesをやってみる
この記事を見てtype-challengesいいなと思ったのでやってみる
リポジトリ
お試し「Hello World」
メモ
この問題はHelloWorld
型を変えて、以下の型のエラーを直すこと
type HelloWorld = any
type test = Expect<Equal<HelloWorld, string>> // エラー
type HelloWorld = any
はHelloWorldという名前を持つ新しい型(型エイリアス)を作っている。
HelloWorld型はany型と同等になっているが、HelloWorld型とstring型と同じになることが求められているため、type HelloWorld = string
にして、string型と同等にすればいい
型エイリアス
TypeScriptでは、型に名前をつけられます。名前のついた型を型エイリアス(タイプエイリアス; type alias)と呼びます。
回答
- type HelloWorld = any
+ type HelloWorld = string
おまけ
この問題で使っている型を見てみる
Expect型
extendsを使って、Expectの渡されるジェネリクスの型Tをtrueに限定している→true以外が渡されると型エラーが起きる。
Equal型
conditional typesを使っていて、(<T>() => T extends X ? 1 : 2)
と(<T>() => T extends Y ? 1 : 2)
が同じ型ならtrue、違う場合はfalseを返す。
どうやらConditional Type内の<T>() => T
が決まらず、遅延評価されるので(<T>() => T extends X ? 1 : 2) extends(<T>() => T extends Y ? 1 : 2)
のまま比較されるみたい。
それでなんやかんやあって、isTypeIdenticalTo
というtypescriptの内部的な実装が===
を用いて厳密な等価評価をしてくれる感じ?
「type Equal<X, Y> = X extends Y ? true : false
でもいけるのでは」と思ったがEqual<true, boolean>
とかも通っちゃうのでダメだった残念
参考にさせていただいた記事
ジェネリクス
型の引数のようなもの。型の安全性とコードの共通化を両立できる。
extends
TypeScriptではextendsキーワードを用いることでジェネリクスの型Tを特定の型に限定することができます。
conditional types
型の三項演算子みたいなイメージで、extendsで制限した型になっているかどうかで返す値を変える。
初級「Pick」
メモ
課題は組み込みの型ユーティリティPick<T, K>
を使用せず、T
からK
のプロパティを抽出する型を実装すること
まず、PickのKにはTで与えたオブジェクトの型のkeyを渡すのでkeyofを使って、K extends keyof T
と表せる。
- type MyPick<T, K> = any
+ type MyPick<T, K extends keyof T> = any
またKはunion型で渡されるので、mapped typesが使えそう。
なのでこんな感じ↓
- type MyPick<T, K extends keyof T> = any
+ type MyPick<T, K extends keyof T> = { [key in K]: T[K] }
keyof
keyofはオブジェクトの型からプロパティ名を型として返す型演算子です。
https://typescriptbook.jp/reference/type-reuse/keyof-type-operator
インデックス型
オブジェクトのフィールド名をあえて指定せず、プロパティのみを指定したい場合に使う。
フィールド名の型はstring、number、symbolのみが指定できる。
型変数はKやkeyにするのが一般的。
let obj: {
[K: string]: number;
};
ユニオン型
TypeScriptのユニオン型(union type)は「いずれかの型」を表現するものです。
ユニオン型の型注釈は、2つ以上の型をパイプ記号(|)で繋げて書きます。
Mapped Types
ユニオン型と組み合わせて使い、インデックス型と同じようにキーの制約として使用することができる。インデックス型と異なり、追加のプロパティが定義できない。
Mapped Typesに現れるin
はユニオン型を反復処理するために使用される。
type MappedType = {
[key in "foo" | "bar"]: string;
};
回答
type MyPick<T, K extends keyof T> = { [key in K]: T[K] }
おまけ
MyPick<T, K>
の部分を変えずにasを使って実装しているのがあった、面白い!
type MyPick<T, K> = { [ P in keyof T as P extends K ? P : never ] : T[P] };
でも適当なstringを与えても通っちゃうっぽい😇
型アサーション「as」(type assertion)
TypeScriptには、型推論を上書きする機能があります。その機能を型アサーション(type assertion)と言います。
初級「ReadOnly」
メモ
組み込みの型ユーティリティReadonly<T>
を使用せず、T
のすべてのプロパティを読み取り専用にする型を実装すること
type MyReadonly<T> = any
readonlyが使えそう。
Tのkeyをkeyofでユニオン型にして、Mapped Typesを使えばいけそう
- type MyReadonly<T> = any
+ type MyReadonly<T> = { readonly [K in keyof T]: T[K] }
readonly
TypeScriptの型システムでは、インターフェース上の個々のプロパティをreadonlyとしてマークすることができる。これにより、予期せぬ変更を防げる。
回答
type MyReadonly<T> = { readonly [K in keyof T]: T[K] }
初級「Tuple to Object」
メモ
課題はタプルを受け取り、その各値のkey/valueを持つオブジェクトの型に変換する型を実装すること
これもMapped Typesを使って、一旦こんな感じ↓
- type TupleToObject<T extends readonly any[]> = any
+ type TupleToObject<T extends readonly any[]> = { [K in keyof T]: T[K] }
でもこれだと[K in keyof T]
の部分が配列のkeyになってしまうので、valueにしたい
配列の型[number]
で配列のvalueをユニオン型で取得できるのか!
- type TupleToObject<T extends readonly any[]> = { [K in keyof T]: T[K] }
+ type TupleToObject<T extends readonly any[]> = { [K in T[number]]: K }
配列の型[number]
を使って、valueでもKを使うようにして完成!
回答
type TupleToObject<T extends readonly any[]> = { [K in T[number]]: K }
おまけ
他の方の回答を見ると、Tの型を制限している。
確かにObjectの配列とかは対応できてないから制限したほうがいいなー
type TupleToObject<T extends readonly (string | symbol | number)[]> = {
[P in T[number]]: P
}
symbol型
JavaScriptのsymbol型はプリミティブ型の一種で、その値が一意になる値です。boolean型やnumber型は値が同じであれば、等価比較がtrueになります。一方、シンボルはシンボル名が同じであっても、初期化した場所が違うとfalseになります。
あまり使う機会はなさそう
JavaScriptにシンボルが導入された動機は、JavaScriptの組み込みAPIの下位互換性を壊さずに新たなAPIを追加することでした。要するに、JavaScript本体をアップデートしやすくするために導入されたものです。したがって、アプリケーションを開発する場合に限っては、シンボルを駆使してコードを書く機会はそう多くはありません。
初級「First of Array」
メモ
課題は配列T
を受け取り、その最初のプロパティの型を返すFirst<T>
を実装すること
とりあえず、T[0]
で最初のプロパティの型を返すようにした
- type First<T extends any[]> = any
+ type First<T extends any[]> = T[0]
[]
を渡したときにnever
を返すようにしたいのでconditional typesを使う
- type First<T extends any[]> = T[0]
+ type First<T extends any[]> = T extends [] ? never : T[0]
配列の1つの要素の型を取得する
回答
type First<T extends any[]> = T extends [] ? never : T[0]
おまけ
この問題結構色々なやり方してる人がいた
lengthがArrayのプロパティだから、T['length']
みたいな書き方もできる感じかな?面白い
type First<T extends any[]> = T['length'] extends 0 ? never : T[0]
inferを使って配列の1つ目の要素の型を抽出している。inferで要素がなかったときだけnever型になるのを利用してておしゃれ
type First<T extends any[]> = T extends [infer A, ...infer rest] ? A : never
なるほど、T[number]
で[]
の時はneverになるのか
type First<T extends any[]> = T[number] extends never ? never : T[0];
2個目のやつのunknownバージョン。改めてスプレッド構文的な感じで書けるんだー
anyじゃなくしてるのもいい感じ
type First<T extends unknown[]> = T extends [infer U, ...unknown[]] ? U : never
never型
TypeScriptのneverは「値を持たない」型。
neverへは何も代入できないが、neverは何にでも代入できる。
常に例外を起こす関数の戻り値に使える。
戻り値の意味として、voidは関数が最後まで実行されるという意味だが、neverは関数の処理が中断、もしくは、永遠に続くことを意味する。
infer
特定の型の一部を抽出し、それを型変数として取得するために使用される。
inferを使える場所はconditional typesのextendsの条件部分に限定される。
型を抽出できなかった場合はnever型になる。
型のスプレッド構文
配列の型が展開?される
初級「Length of Tuple」
メモ
課題はタプルT
を受け取り、そのタプルの長さを返す型Length<T>
を実装すること
一個前の問題の回答であったT['length']
でそのままいけそう
- type Length<T> = any
+ type Length<T> = T['length']
型エラーでた。配列じゃない場合も考慮しなければ!
conditional typesで配列の時と配列じゃない時を分ける
- type Length<T> = T['length']
+ type Length<T> = T extends any[] ? T['length'] : 1
まだエラー出てる。as const
を使ってるやつを渡してるから、readonly
が必要だった!
- type Length<T> = T extends any[] ? T['length'] : 1
+ type Length<T> = T extends readonly any[] ? T['length'] : 1
あ、逆にエラー出さなきゃいけなかったんだ
// @ts-expect-error
Length<5>,
// @ts-expect-error
Length<'hello world'>,
てことでextendsを前に移動
- type Length<T> = T extends readonly any[] ? T['length'] : 1
+ type Length<T extends readonly any[]> = T['length']
T['length']
無しのは今の自分にはちょっと思いつかなかった
constアサーション
オブジェクトリテラルの末尾にas constを記述すればプロパティがreadonlyでリテラルタイプで指定した物と同等の扱いになります。
https://typescriptbook.jp/reference/values-types-variables/const-assertion
回答
type Length<T extends readonly any[]> = T['length']
おまけ
他の人の回答も見てみる
一応unknown使ってるやつ、unknown使えるならunknownのほうがいいかもなー
type Length<T extends readonly unknown[]> = T["length"];
inferでlengthのリテラル型出すやり方。これは思いつかなかった、inferまだ慣れてないなー
type Length<T extends any> = T extends { length : infer R } ? R : never;
上のやつ、正しくは以下になりそう。改めてみると配列のみに制限しているから、lengthは絶対存在するのに条件分岐するのあまりスマートじゃないな
type Length<T extends readonly unknown[]> = T extends { length : infer R } ? R : never;
リテラル型
TypeScriptではプリミティブ型の特定の値だけを代入可能にする型を表現できます。そのような型をリテラル型と呼びます。
unknown型
型が何かわからないときに使う型安全なany型。
unknown型にはどのような値も代入できる。
any型はどのような型の変数にも代入できるが、unknown型の値は具体的な型へ代入できず、プロパティへのアクセス、メソッドの呼び出しも許されない。
初級「Exclude」
メモ
課題は組み込みの型ユーティリティExclude <T, U>
を使用せず、U
に割り当て可能な型をT
から除外する型を実装すること
今までよりちょっとむずい感あるが、とりあえずUはTのどれかに制限する。
- type MyExclude<T, U> = any
+ type MyExclude<T, U extends T> = any
うーん、思いつかない
回答こんな感じだった。
type MyExclude<T, U> = T extends U ? never : T
Uを制限しないのもちょっとモヤモヤ感あるけどテストケース通ればいい感じか
T extends U ? never : T
でUにないやつをTから取り除けるのかー
conditional typesの理解足りないかも
調べたらそれっぽいの出てきた↓
Distributive Conditional Types
conditional typesがジェネリクスに作用する際に、ユニオン型の場合は展開される的な感じかな?
T extends U ? never : T
でTが'A' | 'B'
、Uが'B'
の時に('A' extends 'B' ? never : 'A') | ('B' extends 'B' ? never : 'B')
になるみたいな
回答
type MyExclude<T, U> = T extends U ? never : T
初級「Awaited」
メモ
課題は、例えば:Promise<ExampleType>
という型があった時にExampleType
を取得する型を作る
inferを使って、例でいうExampleType
の部分を取得できそう
- type MyAwaited<T> = any
+ type MyAwaited<T> = T extends Promise<infer R> ? R : never
ただこれだとPromise<Promise<Promise<string>>>
とか多重のやつは対応できない
再帰関数みたいな感じでやれそうかも
- type MyAwaited<T> = T extends Promise<infer R> ? R : never
+ type MyAwaited<T> = T extends Promise<infer U> ? MyAwaited<U> : T
あとtype T = { then: (onfulfilled: (arg: number) => any) => any }
にも対応しなきゃいけない
- type MyAwaited<T> = T extends Promise<infer U> ? MyAwaited<U> : T
+ type MyAwaited<T> = T extends Promise<infer R> ? R : never
普通のプログラミングのイメージに近づけると思いつきやすいなと感じた
めちゃくちゃ無理やりやった。
- type MyAwaited<T> = T extends Promise<infer R> ? R : never
+ type MyAwaited<T extends Promise<any> | ((arg: any) => any) | { [key: string]: any }> =
+ T extends Promise<infer U> | ((arg: infer U) => any)
+ ? U extends Promise<any> | ((arg: any) => any)
+ ? MyAwaited<U>
+ : U
+ : T extends { [key: string]: infer U }
+ ? U extends Function
+ ? MyAwaited<U>
+ : T
+ : never
すごい普通にプログラム書いてる感覚で書けるようになってきたのは良かったが、絶対もっと簡単にかけるはず、、、
回答
type MyAwaited<T extends Promise<any> | ((arg: any) => any) | { [key: string]: any }> =
T extends Promise<infer U> | ((arg: infer U) => any)
? U extends Promise<any> | ((arg: any) => any)
? MyAwaited<U>
: U
: T extends { [key: string]: infer U }
? U extends Function
? MyAwaited<U>
: T
: never
おまけ
回答見てみたらtype T = { then: (onfulfilled: (arg: number) => any) => any }
の条件をクリアしてるのが全然なかった。最近問題が変わったみたい?
組み込みのPromiseLike
というまさに{ then: (onfulfilled: ~
みたいな感じの型があって、それを使ってるみたい。やっぱ組み込み便利だな〜
type MyAwaited<T extends PromiseLike<any | PromiseLike<any>>> =
T extends PromiseLike<infer V>
? V extends PromiseLike<any>
? MyAwaited<V>
: V
: never
個人的にtypeを分けているのはいい感じで関数を細かく分けたりするのに近いものを感じる。真似していきたい。ただ、Thenableの汎用性が低いのがちょっと微妙かも?
then: (onfulfilled: (arg: T) => unknown) => unknown
を完全に別物としてやるのを思いつかなかった
type Thenable<T> = {
then: (onfulfilled: (arg: T) => unknown) => unknown;
}
type MyAwaited<T extends Thenable<any> | Promise<any>> = T extends Promise<infer Inner>
? Inner extends Promise<any> ? MyAwaited<Inner> : Inner
: T extends Thenable<infer U> ? U : false
問題が少し変わって難しくなったみたいだった。実際難しかったけどなんとなくコツが掴めてきたかも?
PromiseLike
初級「If」
メモ
課題はCが truthy である場合の戻り値の型T、Cが falsy である場合の戻り値の型Fを受け取るIfを実装すること
type If<C, T, F> = any
とりあえずCをbooleanに制限する
- type If<C, T, F> = any
+ type If<C extends boolean, T, F> = any
あとはconditional typesを使って、trueかどうかで分岐させる
- type If<C extends boolean, T, F> = any
+ type If<C extends boolean, T, F> = C extends true ? T : F
回答
type If<C extends boolean, T, F> = C extends true ? T : F
初級「Concat」
メモ
課題はJavaScript のArray.concat
関数を型システムに実装すること
とりあえずT,Uを配列に制限する
- type Concat<T, U> = any
+ type MyArray = unknown[] | readonly unknown[]
+ type Concat<T extends MyArray, U extends MyArray> = any
mapped types使えそうってことで途中経過
type MyArray = unknown[] | readonly unknown[]
- type Concat<T extends MyArray, U extends MyArray> = any
+ type Concat<T extends MyArray, U extends MyArray> = { [K in keyof T]: T[K] }
あー閃いた、スプレッド構文でいけます
type MyArray = unknown[] | readonly unknown[]
- type Concat<T extends MyArray, U extends MyArray> = { [K in keyof T]: T[K] }
+ type Concat<T extends MyArray, U extends MyArray> = [...T, ...U]
回答
type MyArray = unknown[] | readonly unknown[]
type Concat<T extends MyArray, U extends MyArray> = [...T, ...U]
おまけ
unknown[]型はreadonly unknown[]型に割り当て可能だからなくても良さそうだった
type Concat<T extends readonly unknown[], U extends readonly unknown[]> = [...T, ...U]
初級「Includes」
課題はJavaScriptのArray.include
関数を型システムに実装すること
T[number]
とconditional types組み合わせればいけそう
- type Includes<T extends readonly any[], U> = any
+ type Includes<T extends readonly any[], U> = U extends T[number] ? true : false
これだとIncludes<[1 | 2], 1>
みたいなのがうまくいかなかった。それぞれの型で厳密に比較する必要がありそう
厳密比較するためにmapped typesと組み合わせてみたけどうまくいかず、、、
- type Includes<T extends readonly any[], U> = U extends T[number] ? true : false
+ type StrictExtend<T, U> = [T] extends [U] ? [U] extends [T] ? U : never : never;
+ type Includes<T extends readonly any[], U> = U extends { [K in keyof T]: StrictExtend<T[K], U> }[number] ? true : false
思いつかなかったため、回答を確認。まだまだだ、、、
再帰は盲点だった!inferと再帰的なのを使って先頭から順番に取ってきてる。
type Includes<T extends readonly any[], U> = T extends [infer First, ...infer Last] ?
Equal<U, First> extends true ?
true :
Includes<Last, U> :
false
回答
type Includes<T extends readonly any[], U> = T extends [infer First, ...infer Last] ?
Equal<U, First> extends true ?
true :
Includes<Last, U> :
false
初級「Push」
初級「Unshift」
初級「Parameters」
メモ
課題は組み込みの型ユーティリティParameters<T>を使用せず、Tからタプル型を構築する型を実装すること
...args: any[]
のany[]
の部分をinfer使って取り出せばいけそう
- type MyParameters<T extends (...args: any[]) => any> = any
+ type MyParameters<T extends (...args: any[]) => any> = T extends (...args: infer R) => any ? R : never
anyの部分、unknownにしても良かったかも
これにて初級終了!結構難易度に差があった
回答
type MyParameters<T extends (...args: any[]) => any> = T extends (...args: infer R) => any ? R : never
中級「Get Return Type」
メモ
課題は組み込みの型ユーティリティReturnType<T>
を使用せず、T
の戻り値の型を取得する型を実装すること
戻り値の部分をinferで取り出してあげれば良さそう
- type MyReturnType<T> = any
+ type MyReturnType<T extends (...args: unknown[]) => unknown> = T extends (...args: unknown[]) => infer R ? R : never
ただこれだと戻り値がユニオン型だとうまくいかないみたい
- type MyReturnType<T extends (...args: unknown[]) => unknown> = T extends (...args: unknown[]) => infer R ? R : never
+ type MyReturnType<T extends (...args: any[]) => unknown> = T extends (...args: any[]) => infer R ? R : never
anyにしたらいけた
戻り値がユニオン型だとうまくいかないんじゃなくて、unknownをbooleanに入れようとして怒られていた
unknown使わずにいけないものかと考えたが、boolean使えば良さそうってなった
- type MyReturnType<T extends (...args: unknown[]) => unknown> = T extends (...args: unknown[]) => infer R ? R : never
+ type MyReturnType<T extends (...args: boolean[]) => unknown> = T extends (...args: boolean[]) => infer R ? R : never
初の中級、ちょっと苦戦した
回答
type MyReturnType<T extends (...args: boolean[]) => unknown> = T extends (...args: boolean[]) => infer R ? R : never
中級「Omit」
メモ
課題は組み込みの型ユーティリティOmit<T, K>を使用せず、TのプロパティからKを削除する型を実装すること
とりあえず、KはTのkeyに制限して、mapped typesを使ってみる
- type MyOmit<T, K> = any
+ type MyOmit<T, K extends keyof T> = { [key in keyof T]: T[key] }
あとは[key in keyof T]
のkeyof T
からKを省けばよさそうなので、Excludeを実装する
- type MyOmit<T, K extends keyof T> = { [key in keyof T]: T[key] }
+ type MyExclude<T, K extends T> = T extends K ? never : T
+ type MyOmit<T, K extends keyof T> = { [key in MyExclude<keyof T, K>]: T[key] }
回答
type MyExclude<T, K extends T> = T extends K ? never : T
type MyOmit<T, K extends keyof T> = { [key in MyExclude<keyof T, K>]: T[key] }
おまけ
asを使うことで、keyの方でもconditional typesが使えて、keyをneverにすると消せる感じなのか
type MyOmit<T, K extends keyof T> = {[P in keyof T as P extends K ? never: P] :T[P]}
中級「Readonly 2」
メモ
課題はK
が指定されている場合に、T
の中のK
のプロパティのみを読み取り専用にし、K
が指定されていない場合は、通常のReadonly<T>
と同様に、すべてのプロパティを読み取り専用にするMyReadonly2<T, K>
を実装すること
まずKの値を制限し、Kのデフォルトの型を設定することで省略して書けるようにする
省略した場合はkeyof Tとなるので全部readonlyにする。Kで指定したもの以外はまだ取り出せていない
- type MyReadonly2<T, K> = any
+ type MyReadonly2<T, K extends keyof T = keyof T> = { readonly [key in K]: T[key] }
あとはKで指定されていない値をOmitで取り出して、それを合成すればいい
- type MyReadonly2<T, K extends keyof T = keyof T> = { readonly [key in K]: T[key] }
+ type MyOmit<T, K extends keyof T> = { [key in keyof T as key extends K ? never : key]: T[key] }
+ type MyReadonly2<T, K extends keyof T = keyof T> = { readonly [key in K]: T[key] } & MyOmit<T, K>
中級は初級で作ったやつの組み合わせって感じがする
ジェネリクスのデフォルト型引数
intersection型
回答
type MyOmit<T, K extends keyof T> = { [key in keyof T as key extends K ? never : key]: T[key] }
type MyReadonly2<T, K extends keyof T = keyof T> = { readonly [key in K]: T[key] } & MyOmit<T, K>
おまけ
Exclude使ったパターンもあった
type MyExlude<T, K> = T extends K ? never : T;
type MyReadonly2<T, K extends keyof T = keyof T> = { readonly [k in K]: T[k]} & { [k in MyExlude<keyof T, K>]: T[k] }
1行で書くならOmitをバラして、こんな感じ
type MyReadonly2<T, K extends keyof T = keyof T> = {
[key in keyof T as key extends K? never: key]: T[key]
} & {
readonly [key in K]: T[key]
}
中級「Deep Readonly」
課題はオブジェクトのすべてのパラメーター(およびそのサブオブジェクトを再帰的に)を読み取り専用にするDeepReadonly<T>を実装すること
メモ
再帰的に読み取り専用にするのでconditional typesと組み合わせて再帰を使う
オブジェクトなのでmapped typesを使う
途中経過がいったんこんな感じ↓
- type DeepReadonly<T> = any
+ type DeepReadonly<T> = T extends Object
+ ? { readonly [key in keyof T]: T[key] extends Object | any[] ? DeepReadonly<T[key]> : T[key] }
+ : never
{ a: ()=> any }
みたいなのが{ a: {} }
になっちゃってる
()=>any
はObject型を継承してるみたいなので、条件追加
- type DeepReadonly<T> = T extends Object
- ? { readonly [key in keyof T]: T[key] extends Object | any[] ? DeepReadonly<T[key]> : T[key] }
- : never
+ type DeepReadonly<T> = T extends Object
+ ? { readonly [key in keyof T]: T[key] extends () => any ? T[key] : T[key] extends Object | any[] ? DeepReadonly<T[key]> : T[key] }
+ : never
回答
type DeepReadonly<T> = T extends Object
? { readonly [key in keyof T]: T[key] extends () => any ? T[key] : T[key] extends Object | any[] ? DeepReadonly<T[key]> : T[key] }
: never
おまけ
keyof T[K]
でObject、Arrayでないかの分岐をしているのか
こっちの方が簡潔でいいな
type DeepReadonly<T> = {
readonly [K in keyof T]: keyof T[K] extends never ? T[K] : DeepReadonly<T[K]>
}
中級「Tuple to Union」
メモ
課題はタプルの値からユニオン型を生成するTupleToUnion<T>を実装すること
配列に制限して、T[number]で終わり
- type TupleToUnion<T> = any
+ type TupleToUnion<T extends unknown[]> = T[number]
めっちゃすぐできたのでunknownも使わないのも考えた
inferでvalueの部分取り出すだけ
type TupleToUnion<T> = T extends { [key in keyof T]: infer R } ? R : never
おまけ
ジェネリック型の配列を表すための組み込み型コンストラクタであるArrayを使っている
{ [key in keyof T]: infer R }
より綺麗
type TupleToUnion<T> = T extends Array<infer R> ? R : never
Array<T>
中級「Chainable Options」
メモ
課題はオブジェクトでもクラスでも何でもいいので、 option(key, value) と get() の 2 つの関数を提供する型を定義すること
- option では、与えられたキーと値を使って現在の config の型を拡張でき、最終的な結果は get で取得する
-
key
はstring
のみを受け付け、value
は任意の型を受け付けると仮定してもOK - 同じ
key
が 2 回渡されることはない
いったんoptionの返り値に渡されたkeyとvalueで作ったオブジェクトの型を設定し、getでoptionの返り値の型を取得する方針でいこうと思う
- type Chainable = {
- option(key: string, value: any): any
- get(): any
- }
+ type MethodReturnType<T> = T extends Array<infer R> ? R : never
+ type Chainable = {
+ option<T extends string, K>(key: T, value: K): { [key in T]: K }
+ get(): MethodReturnType<Chainable['option']>
+ }
ただこれではoptionを連鎖させても、optionの返り値が保持されていない
この後色々試したがかなり時間がかかってしまったため、回答を確認した
ジェネリクスのデフォルト型引数に気づけなかった、、、
これが分かれば後はChainableを再起的な感じで使ってTに値を保持するだけ
type Chainable<T = {}> = {
option<K extends string, V>
(key: K extends keyof T ? never : K, value: V) : Chainable<T & {[key in K]: V}>
get(): T
}
回答
type Chainable<T = {}> = {
option<K extends string, V>
(key: K extends keyof T ? never : K, value: V) : Chainable<T & {[key in K]: V}>
get(): T
}
中級「Last of Array」
課題は配列 T
を受け取り、その最後の要素の型を返す汎用的な Last<T>
を実装すること
スプレッド構文とinferを併用するだけ
- type Last<T extends any[]> = any
+ type Last<T extends unknown[]> = T extends [...infer rest, infer R] ? R : never
回答
type Last<T extends unknown[]> = T extends [...infer rest, infer R] ? R : never
おまけ
[T["length"]]
で元々の配列の要素数+1番目を取得している
配列を1つ先頭に増やすことでそれを可能にしている、面白い
type Last<T extends any[]> = [any, ...T][T["length"]];
中級「Pop」
メモ
課題は配列 T
を受け取り、最後の要素を除いた配列を返す汎用的な Pop<T>
を実装すること
これもスプレッド構文とinferを併用するだけ
また、空配列が渡されたら空配列を返すようにする
- type Pop<T extends any[]> = any
+ type Pop<T extends unknown[]> = T extends [...infer R, infer _] ? R : []
使わない部分のinferを_
にするの見た目的に良さげなので使ってみた
回答
type Pop<T extends unknown[]> = T extends [...infer R, infer _] ? R : []
中級「Promise.all」
メモ
課題はPromise ライクなオブジェクトの配列を受け取る関数 PromiseAll
に型を付けること
- 戻り値は
Promise<T>
である必要がある
まず、渡された引数の型をPromiseにしてそのまま返す感じで実装してみる
1つ目は通ると思ったけど、readonlyがついててうまくいかないみたい
- declare function PromiseAll(values: any): any
+ declare function PromiseAll<T>(values: T): Promise<T>
readonlyがついている場合は-readonly
にして修飾子の削除を行うようにする
- declare function PromiseAll<T>(values: T): Promise<T>
+ type RemoveReadOnly<T> = { -readonly [key in keyof T]: T[key] }
+ declare function PromiseAll<T>(values: T): Promise<RemoveReadOnly<T>>
配列にPromise<number>
みたいなのがある場合はnumberの部分を取り出したいので、inferを使う
type RemoveReadOnly<T> = { -readonly [key in keyof T]: T[key] }
+ type ReturnPromise<T> = { [key in keyof T]: T[key] extends Promise<infer R> ? R : T[key] }
- declare function PromiseAll<T>(values: T): Promise<RemoveReadOnly<T>>
+ declare function PromiseAll<T>(values: T): Promise<RemoveReadOnly<ReturnPromise<T>>>
うーん、一個ずつ取り出すやり方が思い付かなかったので回答を見てみる
スプレッド構文で配列の型を1つずつ取得できるみたい
type MyAwaited<T> = T extends Promise<infer R> ? MyAwaited<R> : T;
declare function PromiseAll<T extends unknown[]>(values: readonly [...T]):
Promise<{ [key in keyof T]: MyAwaited<T[key]> }>;
修飾子の削除
回答
type MyAwaited<T> = T extends Promise<infer R> ? MyAwaited<R> : T;
declare function PromiseAll<T extends unknown[]>(values: readonly [...T]):
Promise<{ [key in keyof T]: MyAwaited<T[key]> }>;
中級「Type Lookup」
メモ
課題はCat | Dog
という Union 型に共通する type
というフィールドを使って、対応する型を取得すること
typeの部分をinferで取り出して、その取り出した型がTと同じの場合にUを返すようにすれば良さそう
conditional typesがジェネリクスに作用する際に、ユニオン型の場合は展開される(Distributive Conditional Types)を利用する
- type LookUp<U, T> = any
+ type LookUp<U, T> = U extends { type: infer R } ? R extends T ? U : never : never
回答
type LookUp<U, T> = U extends { type: infer R } ? R extends T ? U : never : never
おまけ
めっちゃシンプルな回答があった
infer使って取り出さなくても、Tの値で絞り込めたか
type LookUp<U, T> = U extends { type: T } ? U : never;
TはUnion型ではないけどmapped types使ってやってる
上のやつをちょっと回りくどくした感あるな
type LookUp<U, T extends string> = {
[K in T]: U extends { type: T } ? U : never
}[T]
中級「Trim Left」
メモ
課題は文字列を受け取り、先頭の空白を削除した新しい文字列を返す TrimLeft<T>
を実装すること
${infer A}${infer B}
のテンプレートリテラル型を使うことで、Aに先頭の文字列が入り、Bが残りの文字列となる
これを利用して' '
や'\n'
、'\t'
の場合は削除するようにする
- type TrimLeft<S extends string> = any
+ type TrimLeft<S extends string> = S extends `${infer A}${infer B}` ? A extends ' '|'\n'|'\t' ? TrimLeft<B> : S : S
回答
type TrimLeft<S extends string> = S extends `${infer A}${infer B}` ? A extends ' '|'\n'|'\t' ? TrimLeft<B> : S : S
おまけ
Spaceのものかどうか分かればいいので、先頭の文字列をinferで取り出す必要はないってことか
賢い
type Space = ' ' | '\n' | '\t'
type TrimLeft<S extends string> = S extends `${Space}${infer R}` ? TrimLeft<R> : S
中級「Trim」
メモ
課題は文字列を受け取り、両端の空白を削除した新しい文字列を返す Trim<T>
を実装すること
TrimLeftみたいにSpaceを定義して、${Space}${infer R}
みたいな感じにして空白を取り除けばいい
今回は両端なので逆パターンも
- type Trim<S extends string> = any
+ type Trim<S extends string> = S extends `${Space}${infer R}` | `${infer R}${Space}` ? Trim<R> : S
Sが${Space}${infer R}
か${infer R}${Space}
のどちらかに互換があればいいのでinferで型名は同じでいい
回答
type Trim<S extends string> = S extends `${Space}${infer R}` | `${infer R}${Space}` ? Trim<R> : S
中級「Capitalize」
メモ
課題は文字列の最初の文字を大文字に変換し、それ以外はそのままにする Capitalize<T>
を実装すること
まず先頭の文字を取り出していじれる状態にする
- type MyCapitalize<S extends string> = any
+ type MyCapitalize<S extends string> = S extends `${infer A}${infer B}` ? `${A}${B}` : S
スマートなやり方が思いつかなかったので力技でやる
+ type ToUpperCase<T> =
+ T extends 'a' ? 'A'
+ : T extends 'b' ? 'B'
+ : T extends 'c' ? 'C'
+ : T extends 'd' ? 'D'
+ : T extends 'e' ? 'E'
+ : T extends 'f' ? 'F'
+ : T extends 'g' ? 'G'
+ : T extends 'h' ? 'H'
+ : T extends 'i' ? 'I'
+ : T extends 'j' ? 'J'
+ : T extends 'k' ? 'K'
+ : T extends 'l' ? 'L'
+ : T extends 'm' ? 'M'
+ : T extends 'n' ? 'N'
+ : T extends 'o' ? 'O'
+ : T extends 'p' ? 'P'
+ : T extends 'q' ? 'Q'
+ : T extends 'r' ? 'R'
+ : T extends 's' ? 'S'
+ : T extends 't' ? 'T'
+ : T extends 'u' ? 'U'
+ : T extends 'v' ? 'V'
+ : T extends 'w' ? 'W'
+ : T extends 'x' ? 'X'
+ : T extends 'y' ? 'Y'
+ : T extends 'z' ? 'Z'
+ : T
- type MyCapitalize<S extends string> = S extends `${infer A}${infer B}` ? `${A}${B}` : S
+ type MyCapitalize<S extends string> = S extends `${infer A}${infer B}` ? `${ToUpperCase<A>}${B}` : S
絶対もっと短くできそう、、、
回答
type ToUpperCase<T> =
T extends 'a' ? 'A'
: T extends 'b' ? 'B'
: T extends 'c' ? 'C'
: T extends 'd' ? 'D'
: T extends 'e' ? 'E'
: T extends 'f' ? 'F'
: T extends 'g' ? 'G'
: T extends 'h' ? 'H'
: T extends 'i' ? 'I'
: T extends 'j' ? 'J'
: T extends 'k' ? 'K'
: T extends 'l' ? 'L'
: T extends 'm' ? 'M'
: T extends 'n' ? 'N'
: T extends 'o' ? 'O'
: T extends 'p' ? 'P'
: T extends 'q' ? 'Q'
: T extends 'r' ? 'R'
: T extends 's' ? 'S'
: T extends 't' ? 'T'
: T extends 'u' ? 'U'
: T extends 'v' ? 'V'
: T extends 'w' ? 'W'
: T extends 'x' ? 'X'
: T extends 'y' ? 'Y'
: T extends 'z' ? 'Z'
: T
type MyCapitalize<S extends string> = S extends `${infer A}${infer B}` ? `${A}${B}` : S
type MyCapitalize<S extends string> = S extends `${infer A}${infer B}` ? `${ToUpperCase<A>}${B}` : S
おまけ
extends羅列よりわかりやすい
interface ToUpperCase {
a: "A"
b: "B"
c: "C"
d: "D"
e: "E"
f: "F"
g: "G"
h: "H"
i: "I"
j: "J"
k: "K"
l: "L"
m: "M"
n: "N"
o: "O"
p: "P"
q: "Q"
r: "R"
s: "S"
t: "T"
u: "U"
v: "V"
w: "W"
x: "X"
y: "Y"
z: "Z"
}
type LowerCase = keyof ToUpperCase
type MyCapitalize<S extends string> = S extends `${infer First extends LowerCase}${infer Rest}` ? `${ToUpperCase[First]}${Rest}` : S
中級「Replace」
メモ
課題は文字列S
に含まれる文字From
をTo
に一度だけ置き換える型Replace<S, From, To>
を実装すること
お馴染みのinferでFrom以外の部分の文字を取得して、Fromの部分だけToに変える
- type Replace<S extends string, From extends string, To extends string> = any
+ type Replace<S extends string, From extends string, To extends string> = S extends `${infer Prefix}${From}${infer Suffix}` ? `${Prefix}${To}${Suffix}` : S
Replace<'foobarbar', '', 'foo'>
だけうまくいかない
空文字列無視するようにする
- type Replace<S extends string, From extends string, To extends string> = S extends `${infer Prefix}${From}${infer Suffix}` ? `${Prefix}${To}${Suffix}` : S
+ type Replace<S extends string, From extends string, To extends string> =
+ From extends ''
+ ? S
+ : S extends `${infer Prefix}${From}${infer Suffix}`
+ ? `${Prefix}${To}${Suffix}`
+ : S
回答
type Replace<S extends string, From extends string, To extends string> =
From extends ''
? S
: S extends `${infer Prefix}${From}${infer Suffix}`
? `${Prefix}${To}${Suffix}`
: S
中級「Replace All」
メモ
課題は文字列S
に含まれる部分文字列From
をTo
に置き換える型ReplaceAll<S, From, To>
を実装すること
さっきのReplaceを再帰的に使ってみる
- type ReplaceAll<S extends string, From extends string, To extends string> = any
+ type ReplaceAll<S extends string, From extends string, To extends string> =
+ From extends ''
+ ? S
+ : S extends `${infer Prefix}${From}${infer Suffix}`
+ ? ReplaceAll<`${Prefix}${To}${Suffix}`, From, To>
+ : S
置き換えた箇所でまた置き換え対象の文字列ができる場合に対応できていなかった
先頭から置き換えてそうなので、Toの後(Suffix)を渡すようにすればいける
type ReplaceAll<S extends string, From extends string, To extends string> =
From extends ''
? S
: S extends `${infer Prefix}${From}${infer Suffix}`
- ? ReplaceAll<`${Prefix}${To}${Suffix}`, From, To>
+ ? `${Prefix}${To}${ReplaceAll<`${Suffix}`, From, To>}`
: S
回答
type ReplaceAll<S extends string, From extends string, To extends string> =
From extends ''
? S
: S extends `${infer Prefix}${From}${infer Suffix}`
? `${Prefix}${To}${ReplaceAll<`${Suffix}`, From, To>}`
: S
中級「Append Argument」
メモ
課題は与えられた関数型 Fn
と任意の型 A
に対して、第一引数に Fn
を取り、第二引数に A
を取り、Fn
の引数に A
を追加した関数型 G
を生成すること
まずFnをFuctionに制限して、inferとスプレッド構文を組み合わせて引数の型を取得する
- type AppendArgument<Fn, A> = any
+ type AppendArgument<Fn extends Function, A> = Fn extends (...args: infer R) => infer S ? (x: A, ...args: R) => S : never;
ただこれだと引数xが先に来てしまっているので後ろに追加したい
...args
で展開する中にxも入れられればいいんじゃね、と思って思いつきで書いた
ラベル付きタプルっていう機能らしい?
- type AppendArgument<Fn extends Function, A> = Fn extends (...args: infer R) => infer S ? (x: A, ...args: R) => S : never;
+ type AppendArgument<Fn extends Function, A> = Fn extends (...args: infer R) => infer S ? (...args: [...args: R, x: A]) => S : never;
ラベル付きタプル
回答
type AppendArgument<Fn extends Function, A> = Fn extends (...args: infer R) => infer S ? (...args: [...args: R, x: A]) => S : never;
おまけ
別にラベル付きでxにする必要なかったみたい
type AppendArgument<Fn extends Function, A> = Fn extends (...args: infer R)=> infer S ? (...args: [...R, A])=> S : never
中級「Permutation」
メモ
課題はUnion 型を Union 型の値の順列を含む配列に変換する順列型を実装すること
ユニオン型を配列にするのが思いつかなかった、、、
回答見てみる
type Permutation<T, U = T> = [T] extends [never]
? []
: (T extends U
? [T, ...Permutation<Exclude<U, T>>]
: [])
あー、リテラル型でconditional typesを使った時に展開されるの忘れてた
[T] extends [never]
のように配列にしているのはnever以外で意図しない値になってしまうからみたい
type P<T> = T extends never ? true : false;
type A1 = P<never> //never
type A2 = P<any> //boolean
type Q<T> = [T] extends [never] ? true : false;
type B1 = Q<never> //true
type B2 = Q<any> //false
[T] extends [never]
もそうだし、UにTを入れておいたりと難易度高くなってきた
回答
type Permutation<T, U = T> = [T] extends [never]
? []
: (T extends U
? [T, ...Permutation<Exclude<U, T>>]
: [])
中級「Length of String」
メモ
課題はString#length と同じように、文字列リテラルの長さを計算する型を作ること
inferで1文字ずつ配列に入れてT['length']
使う感じでいけそう
値の保持は一個前の問題で使ったデフォルト値の代入を使う
- type LengthOfString<S extends string> = any
+ type LengthOfString<S extends string, T extends unknown[] = []> = S extends `${infer A}${infer B}` ? LengthOfString<B, [A, ...T]> : T['length']
だいぶスラスラできた
回答
type LengthOfString<S extends string, T extends unknown[] = []> = S extends `${infer A}${infer B}` ? LengthOfString<B, [A, ...T]> : T['length']
おまけ
Tはstringの配列でよかった
type LengthOfString<
S extends string,
T extends string[] = []
> = S extends `${infer F}${infer R}`
? LengthOfString<R, [...T, F]>
: T['length'];
中級「Flatten」
メモ
課題は受け取った配列をフラット化した型を出力する型を作ること
T[number]と再帰を使って、ユニオン型でフラット化するところまではいけた
- type FlattenUnion<T> = any
+ type FlattenUnion<T> = T extends unknown[] ? FlattenUnion<T[number]> : T
+ type Flatten<T extends unknown[]> = FlattenUnion<T[number]>
ただ順番が変わってしまうし、ユニオン型を配列に変換する方法が思いつかない、、、
inferで配列の1つ目を取得する方法にしたらスムーズにいけた
- type FlattenUnion<T> = T extends unknown[] ? FlattenUnion<T[number]> : T
- type Flatten<T extends unknown[]> = FlattenUnion<T[number]>
+ type Flatten<T extends unknown[], U extends unknown[] = []> = T extends [infer R, ...infer S]
+ ? R extends unknown[]
+ ? Flatten<[...R, ...S], U>
+ : Flatten<[...S], [...U, R]>
+ : U
回答
type Flatten<T extends unknown[], U extends unknown[] = []> = T extends [infer R, ...infer S]
? R extends unknown[]
? Flatten<[...R, ...S], U>
: Flatten<[...S], [...U, R]>
: U
おまけ
以下のような回答もあって、よりシンプルな感じがあって良さげだが@ts-expect-error
を満たしていなかった
type Flatten<T> = T extends []
? []
: T extends [infer First, ...infer Rest]
? [...Flatten<First>, ...Flatten<Rest>]
: [T]
中級「Append to Object」
メモ
課題はインターフェースに新しいフィールドを追加する型を実装すること
普通のmapped typesにUとVを添えるだけ
- type AppendToObject<T, U, V> = any
+ type AppendToObject<T, U extends string, V> = { [key in keyof T | U]: key extends keyof T ? T[key] : V }
回答
type AppendToObject<T, U extends string, V> = { [key in keyof T | U]: key extends keyof T ? T[key] : V }
中級「Absolute」
メモ
課題はsrting, number または bigint を受け取り、正の数を出力する Absolute 型を実装すること
numberとbigintの場合はstringに変換して、stringでマイナスがある場合はinferで取り除いて、それ以外はそのまま
- type Absolute<T extends number | string | bigint> = any
+ type Absolute<T extends number | string | bigint> = T extends number | bigint
+ ? Absolute<`${T}`>
+ : T extends `-${infer R}`
+ ? R : T
回答
type Absolute<T extends number | string | bigint> = T extends number | bigint
? Absolute<`${T}`>
: T extends `-${infer R}`
? R : T
おまけ
numberとbigintの場合はstringに変換する部分が省略できていてすごい
type Absolute<T extends number | string | bigint> = `${T}` extends `-${infer U}` ? U : `${T}`
中級「String to Union」
メモ
課題は受け取った文字列を Union 型に変換する型を実装すること
inferで文字列を1文字ずつ取得して、再帰を使ってユニオン型にすればOK
- type StringToUnion<T extends string> = any
+ type StringToUnion<T extends string> = T extends `${infer R}${infer S}` ? R | StringToUnion<S> : never
回答
type StringToUnion<T extends string> = T extends `${infer R}${infer S}` ? R | StringToUnion<S> : never
中級「Merge」
課題は2つの型をマージして新しい型を作ること
mapped typesを使って、keyが被っている場合は2つ目の方を採用するので今回で言うとSを優先するようにして完成
- type Merge<F, S> = any
+ type Merge<F, S> = { [key in keyof F | keyof S]: key extends keyof S ? S[key] : key extends keyof F ? F[key] : never }
回答
type Merge<F, S> = { [key in keyof F | keyof S]: key extends keyof S ? S[key] : key extends keyof F ? F[key] : never }
中級「KebabCase」
メモ
課題はキャメルケースもしくはパスカルケースの文字列を、ケバブケースに置換する型を実装すること
まず大文字と小文字の対応する型のリストを作って、頭文字だけリストでそのまま置き換えるようにする
+ type Atoa = {
+ A: 'a';
+ B: 'b';
+ C: 'c';
+ D: 'd';
+ E: 'e';
+ F: 'f';
+ G: 'g';
+ H: 'h';
+ I: 'i';
+ J: 'j';
+ K: 'k';
+ L: 'l';
+ M: 'm';
+ N: 'n';
+ O: 'o';
+ P: 'p';
+ Q: 'q';
+ R: 'r';
+ S: 's';
+ T: 't';
+ U: 'u';
+ V: 'v';
+ W: 'w';
+ X: 'x';
+ Y: 'y';
+ Z: 'z';
+ }
- type KebabCase<S> = any
+ type KebabCase<S> = S extends `${infer Initial}${infer R}`
? `${Initial extends keyof Atoa ? Atoa[Initial] : Initial}${R}`
: ''
先頭の文字以外は変換するときに-
が入るので、1文字ずつ置き換える用の型を作って完成
type Atoa = {
A: 'a';
B: 'b';
C: 'c';
D: 'd';
E: 'e';
F: 'f';
G: 'g';
H: 'h';
I: 'i';
J: 'j';
K: 'k';
L: 'l';
M: 'm';
N: 'n';
O: 'o';
P: 'p';
Q: 'q';
R: 'r';
S: 's';
T: 't';
U: 'u';
V: 'v';
W: 'w';
X: 'x';
Y: 'y';
Z: 'z';
}
+ type KebabCaseCommon<S, T extends string = ''> = S extends `${infer R}${infer S}`
+ ? R extends keyof Atoa
+ ? KebabCaseCommon<S, `${T}-${Atoa[R]}`>
+ : KebabCaseCommon<S, `${T}${R}`>
+ : T
type KebabCase<S> = S extends `${infer Initial}${infer R}`
- ? `${Initial extends keyof Atoa ? Atoa[Initial] : Initial}${R}`
+ ? `${Initial extends keyof Atoa ? Atoa[Initial] : Initial}${KebabCaseCommon<R>}`
: ''
回答
type Atoa = {
A: 'a';
B: 'b';
C: 'c';
D: 'd';
E: 'e';
F: 'f';
G: 'g';
H: 'h';
I: 'i';
J: 'j';
K: 'k';
L: 'l';
M: 'm';
N: 'n';
O: 'o';
P: 'p';
Q: 'q';
R: 'r';
S: 's';
T: 't';
U: 'u';
V: 'v';
W: 'w';
X: 'x';
Y: 'y';
Z: 'z';
}
type KebabCaseCommon<S, T extends string = ''> = S extends `${infer R}${infer S}`
? R extends keyof Atoa
? KebabCaseCommon<S, `${T}-${Atoa[R]}`>
: KebabCaseCommon<S, `${T}${R}`>
: T
type KebabCase<S> = S extends `${infer Initial}${infer R}`
? `${Initial extends keyof Atoa ? Atoa[Initial] : Initial}${KebabCaseCommon<R>}`
: ''
おまけ
ユーティリティ型のUncapitalizeを使って、先頭の文字が変化したら(S2 extends Uncapitalize<S2>
がfalseだったら)大文字の前に-
を入れている
基本全ての文字をUncapitalizeに通しているのもあるが、シンプルにまとまっていてすごい
type KebabCase<S extends string> = S extends `${infer S1}${infer S2}`
? S2 extends Uncapitalize<S2>
? `${Uncapitalize<S1>}${KebabCase<S2>}`
: `${Uncapitalize<S1>}-${KebabCase<S2>}`
: S;
Uncapitalize
中級「Diff」
メモ
課題は2つのObjectの差のObjectの型を出す型を作ること
2つのkeyの差分を出して、mapped typesでそれを表示するだけ
- type Diff<O, O1> = any
+ type DiffKey<A, B> = A extends B ? never : A
+ type Diff<O, O1> = {
+ [key in DiffKey<keyof O, keyof O1> | DiffKey<keyof O1, keyof O>]: key extends keyof O ? O[key]: key extends keyof O1 ? O1[key]: never
+ }
valueの方の比較も必要になったらちょっと大変そう
回答
type DiffKey<A, B> = A extends B ? never : A
type Diff<O, O1> = {
[key in DiffKey<keyof O, keyof O1> | DiffKey<keyof O1, keyof O>]: key extends keyof O ? O[key]: key extends keyof O1 ? O1[key]: never
}
おまけ
めちゃくちゃシンプル
keyof (O | O1)
がOまたはO1のkeyの型なので、OとO1の両方とも持っているkeyが取れるのか
(=keyof O & keyof O1
)
オブジェクトの場合は&
で増えるけど、それ以外は基本絞り込まれるんだっけか
type Diff<O, O1> = Omit<O & O1, keyof (O | O1)>
Exclude使ってるやつ
keyof (O & O1)
とか(O & O1)[K]
の書き方いいな
type Diff<O, O1> = {
[K in Exclude<keyof (O & O1), keyof(O | O1)>]: (O & O1)[K]
}
ユーティリティ型使うのが増えてきた