型力強化のためにtype-challengesをやってみた
Easy Pick
Key in K
のところでちょっと考えた
解答
type MyPick<T, K extends keyof T> = {
[Key in K]: T[Key]
}
Easy Readonly
解答
type MyReadonly<T> = {
readonly [K in keyof T]: T[K]
}
感想
EZ
Tuple to Object
解答
type TupleToObject<T extends readonly any[]> = {
[K in T[number]]: K extends infer U ? U : never
}
感想
元もの問題が、タプルをオブジェクトの型に変換するというもの
const tuple = ['tesla', 'model 3', 'model X', 'model Y'] as const
type result = TupleToObject<typeof tuple> // expected { tesla: 'tesla', 'model 3': 'model 3', 'model X': 'model X', 'model Y': 'model Y'}
肝はK extends infer U ? U : never
で、K
の部分の方を推論してくれる
inferは... extends ... ? ... : ...
という形式でしか使用できない
First of Array
解答
type First<T extends any[]> = T extends [(infer U), ...any[]] ? U : never
感想
最初は
type First<T extends any[]> = T[0] extends infer U ? U : never
で対応したけど、これだとFirst<[]>
の場合にundefined
になってしまってチェックに通らなかった
なので、上記のように対応
T
の0番目があれば推論して、できなければnever
を返すようにした
Length of Tuple
解答
type Length<T> = T extends { length: infer U } ? U : never
感想
infer
の使い方がなんとなくわかってきた
Exclude
解答
type MyExclude<T, U> = T extends U ? never : T
感想
これも結構簡単だった
Unionにおいてneverは無視されるので、Excludeしたいやつと合致した時はneverでfilterするようにした
Awaited
解答
type MyAwaited<T> = T extends PromiseLike<infer U> ? MyAwaited<U> : T
感想
再起的にするのが少し手間取った
あとT extends Promise<infer U>
じゃなく、T extends PromiseLike<infer U>
にしないとダメ
If
解答
type If<C, T, F> = C extends true ? T : F
感想
シンプルにextends
で対応
Concat
解答
type Concat<T extends any[], U extends any[]> = [...T, ...U]
解答
こんなことできるのかなって思ったらできた
できたんだ...
Includes
解答
type Includes<T extends readonly any[], U> = T extends [infer First, ...infer Rest] ? Equal<First, U> extends true ? true : Includes<Rest, U> : false
感想
これはちょっと自分では思いつかなかった。。。
type Includes<T extends readonly any[], U> = U extends T[number] ? true : false
ここまでは単純に言ったのだが、
Includes<[{}], { a: 'A' }>
Includes<[{ readonly a: 'A' }], { a: 'A' }>
とかが通らなくて挫折した
Push
解答
type Push<T extends any[], U> = [...T, U]
感想
単純だ...
むしろこれでいいの?
Unshift
解答
type Unshift<T extends any[], U> = [U, ...T]
感想
EZ
Parameters
解答
type MyParameters<T extends (...args: any[]) => any> = T extends (...args: infer U) => any ? U : never
感想
infer
の復習かな
ここから難易度中
Get Return Type
解答
type MyReturnType<T extends (...args: any[]) => any> = T extends (...args: any[]) => infer U ? U : never
感想
今までのことをやればできる
Omit
解答
type MyExclude<T, U> = T extends U ? never : T
type MyOmit<T, K extends keyof T> = {
[P in MyExclude<keyof T, K>]: T[P]
}
感想
Exclude
とEasy Pick
の組み合わせ
Readonly 2
解答
type MyExclude<T, U> = T extends U ? never : T
type MyReadonly2<T, K extends keyof T = keyof T> = {
readonly [P in K]: T[P]
} & {
[Q in MyExclude<keyof T, K>]: T[Q]
}
感想
Omitの応用編
KをReadOnlyでそれ以外を元のままにして合体
Deep Readonly
解答
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object ? T[K] extends Function ? T[K] : DeepReadonly<T[K]> : T[K]
}
感想
もっとスマートな方法がありそう
objectかつFunctionの時を例外としてる
Tuple To Union
解答
type TupleToUnion<T extends any[]> = T[number]
感想
これはEasyなのでは?
よく使う気がする
Chainable Options
解答
type Chainable<options = {}> = {
option<TKey extends string, TVal>(key: TKey extends keyof options ? never : TKey, value: TVal): Chainable<Omit<options, TKey> & {
[key in TKey] : TVal
}>
get(): options
}
感想
これほんとに難易度中...?
急に難しくなりすぎじゃない?
解説
const hoge = a
.option('foo', 123)
.option('bar', { value: 'Hello World' })
.get()
例えば、上記のような場合を考える
この時、
const hoge = a
の時点での型はChainable<{}>
次にoption('foo', 123)
を追加した時の返り値の型を考えてみる
option('foo', 123)
を追加した時、option
関数にはoption<"foo", number>
というGenericsが渡される
この時の引数の方はkey: TKey extends keyof options ? never : TKey, value: TVal
の部分で解釈され、もし仮に既に同じオプション名が追加されていたら、keyがnever型になりエラーになる
ない場合はそのままTKey
型として解釈される
今回の場合だとまだ'foo'というオプション名は追加されていないのでそのまま
{
'foo': number
}
となる
最後にoption
関数の返り値の型の部分
一個前のChainableのoptions
Generics(一個までは何もしてないのでoptions = {}
)からTKey
名のものを削除する(重複削除)、そして新しくTKey: TVal
のペアがoptions
に追加されたものをGenericsとして受け取ったChainableが出来上がる
この時点での型はChainable<{ 'foo': number }>
あとはこの作業の繰り返し
最後にget()
する時は今まで追加してきたoptions
の型を返す
Last of Array
解答
type Last<T extends any[]> = T extends [...infer Rest, infer U] ? U : never
感想
特になし
Pop
解答
type Pop<T extends any[]> = T extends [...infer Rest, infer Pop] ? Rest : []
感想
else
句のところは空配列でよかったのかな?
Promise.all
解答
type MyAwaited<T> = T extends PromiseLike<infer U> ? MyAwaited<U> : T
declare function PromiseAll<Values extends any[]>(values: readonly [...Values]): Promise<{
[K in keyof Values]: MyAwaited<Values[K]>
}>
感想
MyAwaitedとの応用
Type Lookup
解答
type LookUp<U extends { type: string }, T extends string> = U extends {
type: T
} ? U : never
感想
特になし
Trim Left
解答
type TrimLeft<S extends string> = S extends `${infer L}${infer R}` ? (L extends ' ' | '\n' | '\t' ? TrimLeft<R> : S) : S
感想
type TrimLeft<S extends string> = S extends `${infer L}${infer R}`
の時、Genericsとして"str"を渡すとL
にはs
が、R
にはtr
が代入される
Trim
解答
type TrimStr = ' ' | '\n' | '\t'
type TrimRight<S extends string> = S extends `${infer L}${TrimStr}` ? `${TrimRight<L>}` : S
type TrimLeft<S extends string> = S extends`${TrimStr}${infer R}` ? `${TrimLeft<R>}` : S
type Trim<S extends string> = TrimLeft<TrimRight<S>>
感想
左右で再起的にTrimしていく
Capitalize
解説
type MyCapitalize<S extends string> = S extends `${infer First}${infer Rest}` ? `${Uppercase<First>}${Rest}` : ''
感想
最初の一文字をUppercaで大文字に、残りはそのままでjoin
Replace
解答
type Replace<S extends string, From extends string, To extends string> = S extends `${infer Start}${From}${infer End}` ? From extends '' ? S : `${Start}${To}${End}` : S
感想
infer x Template Literalsの挙動をもうちょっと詳しく調べたい
ReplaceAll
解答
type ReplaceAll<S extends string, From extends string, To extends string> = S extends `${infer Start}${From}${infer End}` ? From extends '' ? S : `${Start}${To}${ReplaceAll<End, From, To>}` : S
感想
一回これでやってみたけどだめだった
type ReplaceAll<S extends string, From extends string, To extends string> = S extends `${infer Start}${From}${infer End}` ? From extends '' ? S : ReplaceAll<`${Start}${To}${End}`, From, To> : S
この場合、'foobarfoobar'
の'ob'
を'b'
に変えようとした時、再起的に置換していくのでfbarfbar
になる
なので、後ろの部分だけを再起的に置換していく
Append Argument
解答
type AppendArgument<Fn extends (...args: any[]) => any, A> = Fn extends (...args: infer Params) => infer Returns ? (...args: [...Params, A]) => Returns : never
感想
引数と戻り値をそれぞれinfer
で推測し、引数には第二引数でもらったパラメータを追加して返す
Permutation
解答
type Permutation<Union, Item = Union> =
[Union] extends [never]
? []
: Item extends Item
? [Item, ...Permutation<Exclude<Union, Item>>]
: never
感想
むっず
Length of String
解答
type StringToArray<S extends string> = S extends `${infer Start}${infer Rest}`
? [Start, ...StringToArray<Rest>] : []
type LengthOfString<S extends string> = StringToArray<S>['length']
感想
StringをArrayにしてから'length'を取得
ちなみに
type LengthOfString<S extends string> = S['length']
だとnumberになる。なんでだろうね
Flatten
解答
type Flatten<Array extends any[], Prev extends any[] = []> = Array extends [infer First, ...infer Rest]
? First extends any[]
? Flatten<[...First, ...Rest], Prev>
: Flatten<[...Rest], [...Prev, First]>
: Prev
感想
Genericsの第二引数を保存先として使用しているのが肝
Append to object
解答
type AppendToObject<T, U extends string, V> = {
[Key in U | keyof T]: Key extends keyof T ? T[Key] : V
}
感想
type AppendToObject<T, U extends string, V> = T extends infer Obj ?
Obj & {
[Key in U]: V
} : never
じゃだめだった
[Key in U | keyof T]
って書き方を初めて知った
Absolute
解答
type Absolute<T extends number | string | bigint> = `${T}` extends `${infer First}${infer Rest}`
? First extends '-'
? Rest
: `${T}`
: never
感想
inferだいぶ慣れた
String to Union
解答
type StringToArray<T extends string> = T extends `${infer First}${infer Rest}`
? [First, ...StringToArray<Rest>]
: []
type StringToUnion<T extends string> = StringToArray<T>[number]
感想
一度、Stringを一文字ごとの配列に変換してからUnionへ変換した
Merge
解答
type Merge<F, S> = {
[Key in keyof F | keyof S]: Key extends keyof S ? S[Key] : Key extends keyof F ? F[Key] : never
}
感想
同じKey名がある場合、FがSに上書きされるようにしないといけないので順番に注意
Kebab Case
解答
type KebabCase<S extends string> = S extends `${infer First}${infer Rest}`
? Rest extends Uncapitalize<Rest>
? `${Uncapitalize<First>}${KebabCase<Rest>}`
: `${Uncapitalize<First>}-${KebabCase<Rest>}`
: S
感想
先頭の文字(First)とそれ以外の文字(Rest)の分割
Restの先頭が小文字だったら、Firstを小文字にして残りの文字列をKabebCase
に渡す
Restの先頭が大文字だったら、Firstを小文字にしてハイフンを追加。RestをKebabCase
に渡す
Diff
解答
type Diff<O, O1> = Omit<O & O1, keyof (O | O1)>
感想
シンプルだけど意外と苦戦
O
とO1
が合体したオブジェクトからkeyof (O | O1)
で和集合のキーを指定
AnyOf
解答
type AnyOf<T extends readonly any[]> = T[number] extends '' | 0 | false | undefined | null | [] | { [key: string]: never } ? false : true
感想
とりあえず、Falsyなものを列挙してそれに当てはまればFalse、そうでなければTrueと判定
IsNever
解答
type IsNever<T> = [T] extends [never] ? true : false
感想
Permutation
のところでやったのですぐに分かった
ちなみに
type IsNever<T> = T extends never ? true : false
とすると
type Result = IsNever<never>
の時に、Result = never
となり判定に失敗する
IsUnion
解答
type IsUnionInternal<T, P = T> = T extends infer Union
? P extends Union
? true
: false
: never
type IsUnion<T> = IsUnionInternal<T> extends true ? false : true
解説
T extends infer Union
T(e.g 1 | 2 | 3
)をUnionに推論する
? P extends Union
TをUnionに推論できた時、P(1 | 2 | 3
)がUnionのサブタイプである場合を確認
PはUnionなのでUnionTypesは分解され
P extends Union
は(1 extends Union ...) | (2 extends Union ...) | (3 extends Union ...)
と分解される
もし条件分岐がtrueならtrueをそうでないならfalseを返す
ReplaceKeys
解答
type ReplaceKeys<Source, KeysUnion, Y> = {
[Key in keyof Source]: Key extends KeysUnion ? Key extends keyof Y ? Y[Key] : never : Source[Key]
}
解説
今回はReplaceのみなので
[Key in keyof Source]
で元ObjectのKeyのみを抽出
Key extends KeysUnion ? Key extends keyof Y ? Y[Key] : never : Source[Key]
ObjectのKeyが第二引数のUnionの一部であるかつ元Objectのキーと第三引数のObjectのキーが一致している場合、置換後の方のObjectのValueを使い、ない場合はnever
そもそも置換後のObjectに対象のキーがない場合は元ObjectのValueをそのまま使用する
Remove Index Signature
解答
type RemoveIndexSignature<T> = {
[K in keyof T as PropertyKey extends keyof T[K] ? never : K]: T[K];
}
解説
PropertyKeyは以下のように定義されている
declare type PropertyKey = string | number | Symbol;
Percentage Parser
解答
type Tail<A extends string> = A extends `${infer Middle}${LastStr}`
? [Middle, LastStr]
: A extends `${infer Middle}`
? [Middle, ""]
: ["", ""]
type PercentageParser<A extends string> = A extends `${infer First}${infer Rest}`
? First extends FirstStr
? [First, ...Tail<Rest>]
: ["", ...Tail<`${First}${Rest}`>]
: ["", "", ""]
解説
第一引数(e.g. "+100%")を先頭一文字F(First)とその他(Rest)に分解
Firstが+
もしくは-
の場合、[First, ...Tail<Rest>]
それ以外の場合だとFirstが数字になるので、["", ...Tail<
>]
として処理する
Firstを処理したあと、Tailで処理する(この時点では+
、-
は含まないstringが渡ってくる)
Tailに渡っていた第一引数を%
とそれ以外(Middle)に分解する
もし%
が含まれており、分解できた場合そのまま、数字の部分(Middle)と記号の部分(LastStr)を返す
もし%
が含まれていない場合は、記号の部分を空文字で返す
数字の部分さえない場合はMiddleの部分も空文字にして返す
Drop Char
解答
type DropRight<S extends string, C extends string> = S extends `${infer L}${C}` ? `${DropRight<L, C>}` : S
type DropLeft<S extends string, C extends string> = S extends`${C}${infer R}` ? `${DropLeft<R, C>}` : S
type DropMiddle<S extends string, C extends string> = S extends `${infer L}${C}${infer R}` ? `${DropMiddle<L, C>}${DropMiddle<R, C>}` : S
type DropChar<S extends string, C extends string> = DropLeft<DropMiddle<DropRight<S, C>, C>, C>
解説
Trimの応用
MinusOne
解答
type Tuple<L extends number, T extends unknown[] = []> = T["length"] extends L
? T
: Tuple<L, [...T, unknown]>;
type FromToTuple<L extends number, From extends unknown[] = [], To extends unknown[] = []> = From['length'] extends L
? From // FromのlengthがLと同じ時
: From['length'] extends To['length'] // FromのlengthがLと違う時かつ、FromとToのlengthが同じ時
? never // 終わり
: FromToTuple<L, [unknown, ...From], To> // Fromに要素を一つ追加して再帰
type TupleMax = Tuple<999>
type SuperTuple<L extends number, C extends unknown[] = [], P extends unknown[] = []> = C["length"] extends L
? C // 既に配列の長さがLと同じ時
: FromToTuple<L, P, C> extends never // まだ長さを満たしてない時かつ、LとPのlengthが同じかつ、PとCのlengthが同じ
? SuperTuple<L, [...TupleMax, ...C], C>
: FromToTuple<L, P, C>
type Minus<A extends number> = SuperTuple<A> extends [infer _, ... infer R extends unknown[]] ? R['length'] : never
type MinusOne<T extends number> =
T extends 0
? -1
: Minus<T>
解説
難しかった
普通にやると999以上の数字に対応できないので工夫が必要
PickByType
解答
type PickByType<T, U> = {
[K in keyof T as T[K] extends U ? K : never]: T[K] extends U ? T[K] : never
}
解説
特になし
StartsWith
解答
type StartsWith<T extends string, U extends string> = T extends `${U}${infer Rest}` ? true : false
解説
特になし
EndsWith
解答
type EndsWith<T extends string, U extends string> = T extends `${infer _}${U}` ? true : false
解説
特になし
PartialByKeys
解答
type Flatten<T> = {
[P in keyof T]: T[P]
}
type PartialByKeys<T, K extends keyof T = keyof T> = Flatten<{
[P in Exclude<keyof T, K>]: T[P]
} & {
[P in K]?: T[P]
}>
解説
type PartialByKeys<T, K extends keyof T = keyof T> = {
[P in Exclude<keyof T, K>]: T[P]
} & {
[P in K]?: T[P]
}
だと
type Result = {
address: string;
} & {
name?: string | undefined;
age?: number | undefined;
}
となってしまうので、一度合体させてから平坦にしている