Open16
💻 Type challenge / Medium (4 of 10) やっていくスレ
引っ越し元:
💻 Type challenge / Medium (4 of 10) やっていくスレ
#snaka_type_challenge
このスレでは以下をやっていく:
String to Union / Merge / KebabCase / Diff / AnyOf / IsNever / IsUnion
String to Union
Capitalize でやったのと同じ、テンプレートリテラルの infer で文字列の分解と、再帰によってその分解を再帰的に適用することによって達成できた
type StringToUnion<T extends string> =
T extends `${infer F}${infer Rest}`
? F | StringToUnion<Rest>
: never
Merge
Append to object と同じ考え方で愚直に解いた
type Merge<F, S> = {
[K in keyof F | keyof S]:
K extends keyof S
? S[K]
: K extends keyof F
? F[K]
: never
}
- F の key と S の key を Union でまとめ、K として分配
- K が S のプロパティであれば
S[K]
の型を適用 - K が F のプロパティであれば
F[K]
の型を適用 - そのどちらでも無いというケースは存在し得ない (
keyof F | keyof S
なので ) のでnever
- K が S のプロパティであれば
KebabCase
ちょっと惜しい、文字列の先頭が大文字のケースが対応できていない
type UtoL = {
'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> =
S extends `${infer First}${infer Rest}`
? First extends keyof UtoL
? `-${UtoL[First]}${KebabCase<Rest>}`
: `${First}${KebabCase<Rest>}`
: ''
以下のテストが失敗している
Expect<Equal<KebabCase<'FooBarBaz'>, 'foo-bar-baz'>>,
Expect<Equal<KebabCase<'Foo-Bar'>, 'foo--bar'>>,
Expect<Equal<KebabCase<'ABC'>, 'a-b-c'>>,
評価対象の S
が再帰の一番外側かどうかを識別する Outer
型変数を追加してみた
type UtoL = {
'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, Outer = true> =
S extends `${infer First}${infer Rest}`
? First extends keyof UtoL
? Outer extends true
? `${UtoL[First]}${KebabCase<Rest, false>}`
: `-${UtoL[First]}${KebabCase<Rest, false>}`
: `${First}${KebabCase<Rest, false>}`
: S
一応テストは通ったが... ブラッシュアップできそう
ループ(再帰)と変換を分離してすこし見やすくした
type UtoL = {
'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 Kebab<S, Outer> =
S extends keyof UtoL
? Outer extends true
? UtoL[S]
: `-${UtoL[S]}`
: S
type KebabCase<S, Outer = true> =
S extends `${infer First}${infer Rest}`
? `${Kebab<First, Outer>}${KebabCase<Rest, false>}`
: S
他の人の回答見た感じTypeChallenge で組み込みの Utility Type 使ってもいいんだな...
Diff
愚直にやってみたけどもっとコンパクトにできそう
type Diff<O, O1> = {
[
K in keyof O | keyof O1
as K extends keyof O
? K extends keyof O1
? never
: K
: K
]: K extends keyof O
? O[K]
: K extends keyof O1
? O1[K]
: never
}
- O と O1 のプロパティの key をまとめた Union を作って K にひとつづつ Mapping する
- as の右辺で K が O と O1 両方に存在する key の場合は
never
として型から除外する - それ意外は K を型に含める
- as の右辺で K が O と O1 両方に存在する key の場合は
- 値の型については K が O と O1 それぞに存在する型である場合に
O[K]
,O1[K]
で型を適用- それ以外は
never
とする
- それ以外は
K が O と O1 の両方の key として含まれているかどうかの判定を Intersection に置きかえた
type Diff<O, O1> = {
[
K in keyof O | keyof O1
as K extends keyof O & keyof O1
? never
: K
]: K extends keyof O
? O[K]
: K extends keyof O1
? O1[K]
: never
}
AnyOf
できた、と思ったけどテストが1つ通っていない
type Truthy = true | 1 | [any, ...any] | { [idx: string | number]: any }
type AnyOf<T extends readonly any[]> =
T extends [infer F, ...infer Rest]
? F extends Truthy
? true
: AnyOf<Rest>
: false
以下が通らない
Expect<Equal<AnyOf<[0, '', false, [], {}]>, false>>,