Closed15
💻 Type challenge / Medium (1 of 10) やっていくスレ
引っ越し元:
💻 Type challenge / Medium (1 of 10) やっていくスレ
#snaka_type_challenge
このスレでは以下を対象とする
- Get Return Type
- Omit
- Readonly 2
- Deep Readonly
- Tuple to Union
- Chainable Options
- Last of Array
前スレ
Get Return Type
type MyReturnType<T> =
T extends (...args: any) => infer U
? U
: never
可変引数の書き方がいつもわからなくなるが、なんとかクリアできた
Omit
なんとか自力で解けた
type MyOmit<T, K extends keyof T> = {
[Key in keyof T as Key extends K ? never : Key]: T[Key]
}
いくつかポイントがあった
-
K extends keyof T
でK
に渡す型をT
のプロパティ名に一致するリテラルに限定する -
Key in keyof T
でT
のプロパティ名(key)をひとつづつ取り出してKey
に割り当てる -
[... as Key extends K ? never : Key]
でKey
がK
で示されたリテラルと一致するときはnever
とすることで、そのプロパティを結果から除外する。そうでないときはKey
そのまま - 最後
T[Key]
でT
の型からKey
の名前を持つプロパティの型を取り出す
Readonly2
すこしづつ TypeScript の型システムの気持ちがわかるようになってきたかも
type MyReadonly2<T, K extends keyof T = any> =
{ readonly [U in keyof T as U extends K ? U : never]: T[U] } & // ① K に該当するプロパティだけを抽出 し readonly にする
{ [U in keyof T as U extends K ? never : U]: T[U] } // ② K に該当しないプロパティだけを抽出
考え方のポイント:
- Omit でやったことの応用で、
T
のプロパティからK
に該当するものだけを抽出した型 (①) と、それ意外を抽出した型 (②) を取り出す - ① に
readonly
を付ける - ② には何もつけずそのまま
- ① と ② をマージした型を最終的に作る
-
K
が省略した場合のデフォルトはany
とすることで、全てのプロパティが readonly 対象となる
DeepReadonly
object
から関数を除く部分がちょっと納得行っていない。
もっと良いやりかたありそう
type DeepReadonly<T> = {
readonly [U in keyof T]: T[U] extends object
? T[U] extends (...args: any[]) => any
? T[U]
: DeepReadonly<T[U]>
: T[U]
}
別解を見てみる
type DeepReadonly<T> = keyof T extends never
? T
: { readonly [k in keyof T]: DeepReadonly<T[k]> };
keyof T extends never
で T
が key を持つ存在 ( = object ) かどうかを判定している。
なるほど。
これがよくわからない
type DeepReadonly<T> =
{ readonly [P in keyof T]: T[P] extends { [i: number]: unknown }
? DeepReadonly<T[P]>
: T[P] }
{ [i: number]: unknown }
これで 関数を除いたオブジェクトにマッチする理屈がよくわかっていない
type IsObject<T> = T extends { [i: number]: unknown } ? true : false
type obj0 = IsObject<{ 1: 'a' }> // true
type obj1 = IsObject<[1, 2, 3]> // true
type obj2 = IsObject<[1, 'a', null]> // true
type obj3 = IsObject<{ a: 'A' }> // true
type obj4 = IsObject<() => {}> // false
TupleToUnion
これはすぐ解けた
type TupleToUnion<T extends [...any]> = T[number]
別解も見てみる
type TupleToUnion<T extends any[]> = T[number]
だいたい自分の解答もベストに近いように思われる
Chainable Options
再帰つかえばできそうなイメージがあるが、なかなかうまくいかない。
もうちょっと自力で頑張りたいが一旦パス
Last of Array
これは簡単
type Last<T extends any[]> = T extends [...any, infer U] ? U : never
ほかの人の回答みるといろんな解法があるとわかって面白い
Chanable Options (retry)
自力は諦めて他の人の回答を参考にした
type Chainable<T = {}> = {
option<Key extends string,Value>(
key: Key extends keyof T ? never : Key,
value: Value
): Chainable<T & { [key in Key]: Value }>
get(): T
}
型引数を受け取れる場所が Chanable< ... >
の箇所だけだという思い込みがあった
それもあって、Key extends keyof T ? never : Key
で、既存の key の指定を禁止するという発想ができなかった。
このスクラップは2023/12/09にクローズされました