Mapped Types 元のプロパティから新しいプロパティを生成したり、あるプロパティを除外するためには as 句を使用します。
as 句は 2 通りの使い方があります。1 つ目は以下の例のとおり template literal types を用いてプロパティ名をリネームできます。
type Getters<T> = { [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]};interface Person { name: string; age: number; location: string;}type LazyPerson = Getters<Person>;type LazyPerson = { getName: () => string; getAge: () => number; getLocation: () => string;}
2 つ目の使い方として、as 句の中で never を返した場合にはそのプロパティを除外できます。今回の課題の場合には as 句の中で P が K に対して拡張可能であるか検査しそうであるなら never を返せばよいわけです。
回答例
type MyOmit<T, K extends keyof T> = { [P in keyof T as P extends K ? never : P]: T[P]}
Readonly 2
Implement a generic MyReadonly2<T, K> which takes two type argument T and K.
K specify the set of properties of T that should set to Readonly. When K is not provided, it should make all properties > readonly just like the normal Readonly<T>.
これで K で指定されたプロパティと指定されなかったプロパティどちらも表すことができました。最終的にこれらの型を結合して返したいのですから、交差型(Intersection Types)を使いましょう。交差型は同じプロパティ名を持つとき後ろの型が優先されるので順番が重要です。
type MyReadonly2<T, K extends keyof T> = T & { readonly [P in K]: T[P]}
しかし、この形ではまだ漏れがあります。 K が渡されなかった場合にはすべてのプロパティを readonly にする必要がありますがその要件を満たせていません。
このエラーを解消するために K に対してデフォルト引数を渡します。これは JavaScriptのデフォルト引数と同様の構文です。デフォルト値として T のプロパティの取りうるすべての値を渡せばすべてのプロパティに対して反復処理が行われるため要件を満たすことができます。
回答例
type MyReadonly2<T, K extends keyof T = keyof T> = T & { readonly [P in K]: T[P]}
Deep Readonly
Implement a generic DeepReadonly which make every parameter of an object - and its sub-objects recursively - readonly.
You can assume that we are only dealing with Objects in this challenge. Arrays, Functions, Classes and so on are no need to take into consideration. However, you can still challenge your self by covering different cases as many as possbile.
For example
type X = { x: { a: 1 b: 'hi' } y: 'hey'}type Expected = { readonly x: { readonly a: 1 readonly b: 'hi' } readonly y: 'hey'}const todo: DeepReadonly<X> // should be same as `Expected`
Last of Array では配列の最後の要素だけを取得しました。Pop は配列の最後の要素だけを取り除きます。
回答例
type Pop<T extends any[]> = T extends [...infer P, any] ? P : never
Promise.all
Type the function PromiseAll that accepts an array of PromiseLike objects, the returning value should be Promise<T> where T is the resolved result array.
const promise1 = Promise.resolve(3);const promise2 = 42;const promise3 = new Promise<string>((resolve, reject) => { setTimeout(resolve, 100, 'foo');});// expected to be `Promise<[number, number, string]>`const p = Promise.all([promise1, promise2, promise3] as const)
type Replace<S extends string, From extends string, To extends string> = S extends `${infer L}${From}${infer R}`
あとは文字列にマッチしたなら From を To にそのまま置き換えるだけでよさそうです。文字列にマッチしなかったら元の文字列をそのまま返します。
type Replace<S extends string, From extends string, To extends string> = S extends `${infer L}${From}${infer R}` ? `${L}${To}${R}` : S
しかし、まだ 1 つのテストに失敗します。どうやら From に空文字 '' が渡されると具合が悪いようです。ここは早期リターンのように From が空文字 '' だった場合には早々に元の文字列を返してしまいましょう。
回答例
type Replace<S extends string, From extends string, To extends string> = From extends '' ? S : S extends `${infer L}${From}${infer R}` ? `${L}${To}${R}` : S
ReplaceAll
Implement ReplaceAll<S, From, To> which replace the all the substring From with To in the given string S
For example
type replaced = ReplaceAll<'t y p e s', ' ', ''> // expected to be 'types'
type ReplaceAll<S extends string, From extends string, To extends string> = From extends '' ? S : S extends `${infer L}${From}${infer R}` ? `${ReplaceAll<L, From, To>}${To}${ReplaceAll<R, From, To>}` : S
Append Argument
For given function type Fn, and any type A (any in this context means we don't restrict the type, and I don't have in > mind any type 😉) create a generic type which will take Fn as the first argument, A as the second, and will produce > function type G which will be the same as Fn but with appended argument A as a last one.
For example,
type Fn = (a: number, b: string) => numbertype Result = AppendArgument<Fn, boolean>// expected be (a: number, b: string, x: boolean) => number
type Merge<F extends Record<string, unknown>, S extends Record<string, unknown>> = { [P in keyof F | keyof S]: /** TODO */};
反復処理の中で P が T のプロパティなら F[P] をそうでないなら S[P] を返すようにします。
type Merge<F extends Record<string, unknown>, S extends Record<string, unknown>> = { [P in keyof F | keyof S]: P extends keyof F ? F[P] : S[P] // Type 'P' cannot be used to index type 'S'.};
しかしこれではコンパイルが通りません。さらに P が S のプロパティであるか検査する必要があります。
type Merge<F extends Record<string, unknown>, S extends Record<string, unknown>> = { [P in keyof F | keyof S]: P extends keyof F ? F[P] : P extends keyof S ? S[P] : never};
type result = Merge<Foo, Bar>type result = { a: number; b: string; c: boolean;}
よく見るとプロパティ b は Foo と Bar どちらにも存在します。プロパティが重複する場合には 2 つ目の型のプロパティで上書きする必要があるので b の型は number でなければいけません。
2 つ目の型のプロパティで上書きするようにするには、条件部で先に S が持つプロパティかどうかを検査する必要があります。
回答例
type Merge<F extends Record<string, unknown>, S extends Record<string, unknown>> = { [P in keyof F | keyof S]: P extends keyof S ? S[P] : P extends keyof F ? F[P]: never};