Types Challenge medium 記録
repo
問題
作業myrepo
Get return type
そもそも、このutility関数を知らなかったのでみてみた
こんな感じ。
inferで推論させて、conditional typeで定義
type MyReturnType<T extends (...args: any[]) => unknown> = T extends (
...args: any[]
) => infer U
? U
: never;
Omit
interface Todo {
title: string;
description: string;
completed: boolean;
}
type MyOmit<T, K> = {
[P in Exclude<keyof T, K>]: T[P];
};
type TodoPreview = MyOmit<Todo, "description" | "title">;
const todo: TodoPreview = {
completed: false,
};
excludeを作り、まずプロパティだけ削除してmapped typeで型を定義する
Readonly 2
interface Todo {
title: string;
description: string;
completed: boolean;
}
type MyReadonly2<T, K extends keyof T> = {
readonly [P in K]: T[P];
} & Omit<T, K>;
const todo: MyReadonly2<Todo, "title" | "description"> = {
title: "Hey",
description: "foobar",
completed: false,
};
todo.title = "Hello"; // Error: cannot reassign a readonly property
todo.description = "barFoo"; // Error: cannot reassign a readonly property
todo.completed = true; // OK
Kをmapped typeで展開しながらreadonlyをつける。その上で、残ったかたをTとKからOmitを使って生成し、intersectionで融合
デフォルト型引数というのがあるのを知った。
DeepReadonly
type X = {
x: {
a: 1;
b: "hi";
};
y: "hey";
};
type DeepReadonly<T> = {
readonly [key in keyof T]: T[key] extends object
? DeepReadonly<T[key]>
: T[key];
};
type Todo = DeepReadonly<X>; // should be same as `Expected`
基本的にconditional typeで条件分岐させてobjectかをチェックして、その後再起的に呼び出す。
Tuple to Union
type Arr = ['1', '2', '3']
type TupleToUnion<T extends any[]> = T[number];
type Test = TupleToUnion<Arr> // expected to be '1' | '2' | '3'
T[number]
で展開できる。加えて、引数の型制約をつけておく。
Chainable Options
declare const config: Chainable;
type Chainable<T = {}> = {
option<K extends string, V>(
key: K,
value: V
): Chainable<T & { [key in K]: V }>;
get(): T;
};
const result = config
.option("foo", 123)
.option("name", "type-challenges")
.option("bar", { value: "Hello World" })
.get();
// expect the type of result to be:
interface Result {
foo: number;
name: string;
bar: {
value: string;
};
}
まず<T = {}>
でdefault型引数を設定する。
その後メソッドを定義していき、options()
でT
にmergeしていく。get()
で今までの型を吐き出す。
Last of Array
type arr1 = ["a", "b", "c"];
type arr2 = [3, 2, 1];
type Last<T extends unknown[]> = T extends [...unknown[], infer L] ? L : never;
type tail1 = Last<arr1>; // expected to be 'c'
type tail2 = Last<arr2>; // expected to be 1
Variadic Tuple Typesを使う必要がある。
any[] extends [...unknown[], infer L]
とするとLが最後の要素として取得できる。
pop
回答できた!
type Pop<T extends any[]> = T extends [...infer V, ...infer L] ? V : never;
inferを使うことで取り出すことができる。
Promise.All
できたけどめちゃむずい
declare function PromiseAll<T extends any[]>(values: readonly [...T]): Promise<{ [key in keyof T]: T[key] extends Promise<infer R> ? R : T[key] }>
[P in keyof T]
これでindexを一個ずつPで取得できる。
Lookup
type LookUp<U, T> = U extends Record<"type", string>
? U["type"] extends T
? U
: never
: never;
型制約でU extends Record<"type", string>
を定義するとneverになる
trimLeft
type Space = " " | "\n\t"
type TrimLeft<T extends string> = T extends `${Space}${infer L}` ? TrimLeft<L> : T
むずい。spaceと改行コード、tab文字があったらそれに伴うように再帰で呼び出す。なくなったらTを出す。
trim
type Space = " " | "\n\t" | "\n" | "\t"
type TrimLeft<T extends string> = T extends `${Space}${infer L}` ? TrimLeft<L> : T
type TrimRight<T extends string> = T extends `${infer R}${Space}` ? TrimRight<R> : T
type Trim<T extends string> = TrimLeft<TrimRight<T>>
ちょっと綺麗じゃないけど、できた
case全部対応するためにSpaceの定義変えた
type cases = [
Expect<Equal<Trim<'str'>, 'str'>>,
Expect<Equal<Trim<' str'>, 'str'>>,
Expect<Equal<Trim<' str'>, 'str'>>,
Expect<Equal<Trim<'str '>, 'str'>>,
Expect<Equal<Trim<' str '>, 'str'>>,
Expect<Equal<Trim<' \n\t foo bar \t'>, 'foo bar'>>,
Expect<Equal<Trim<''>, ''>>,
Expect<Equal<Trim<' \n\t '>, ''>>,
]
Capitalize
type MyCapitalize<T extends string> = T extends `${infer First}${infer Rest}` ? `${Uppercase<First>}${Rest}` : T;
これは簡単だったかも
inferの使い方としてはbasic
Replace
type Replace<S extends string, From extends string, To extends string> = From extends "" ? S : S extends `${infer F}${From}${infer Rest}` ? `${F}${To}${Rest}` : S;
文字列の中で特定の文字列を推論させている
${infer F}${From}${infer Rest}
ReplaceAll
答え見たけど難しい
type ReplaceAll<S extends string, From extends string, To extends string> =
From extends ''? S: S extends `${infer F}${From}${infer L}`? `${F}${To}${ReplaceAll<L, From, To>}` : S
とはいえ型パズルを再帰で呼び出して実行しているだけかな。
Append Argument
type AppendArgument<Fn extends (...args: any) => any, A> = Fn extends (...args: infer Args) => infer R ? (...args: [...Args, A]) => R : never
(...args: any) => anyで関数の型になる
// スプレッド構文を型で使うときがいまいちわかりにくい
Permutation
type Permutation<T, U = T> = [T] extends [never] ? [] : U extends T ? [U, ...Permutation<Exclude<T, U>>] : never
これはむずかった。
いまいち[Type]の中の型とType[]の違いがわからない
答え見て改良したが、Tを複数回使いたいけど使えないからU = Tにしているのはアイデアすごい。
length of string
type LengthOfString<S extends string, Arr extends string[] = []> = S extends `${infer Head}${infer Rest}` ? LengthOfString<Rest, [Head, ...Arr]> : Arr['length']
再帰でどんどんHeadの文字を呼び出して最終的に配列にして、Arr["length"]をする。
Flatten
type Flatten<Array extends any[]> =
Array extends [infer First, ...infer Last] ? First extends any[] ? [...Flatten<First>, ...Flatten<Last>] : [First, ...Flatten<Last>] : [];
久々にフルでできた
最初から配列の要素とっていて、それも配列だったら再帰。そうでなければ、その要素とそれ以外の要素の再帰でFlatにする
Append to Object
type AppendToObject<TypeObject, KeyObject extends string, ValueObject> = {
[key in keyof TypeObject | KeyObject]: key extends keyof TypeObject ? TypeObject[key] : ValueObject;
}
型名冗長に書きすぎたけど、これは簡単
Abusolute
type Absolute<T extends number | string | bigint> =
`${T}` extends `-${infer R}` ? R : T extends number ? `${T}` : T extends bigint ? `${T}` : T;