Types challenge easy 記録
types challengeをやっていく
めんどいのでsandbox使って実行していく。
repository Link
ブラウザだとやりづらいので、プロジェクト作成。
Pick
interface Todo {
title: string
description: string
completed: boolean
}
type MyPick<T, K extends keyof T> = {
[P in K]: T[P];
};
type TodoPreview = MyPick<Todo, 'title' | 'completed'>
const todo: TodoPreview = {
title: 'Clean room',
completed: false,
}
console.log(todo);
回答できた。
type MyPick<T, K extends keyof T> = {
[P in K]: T[P];
};
ジェネリクスの部分、Tのプロパティに含まれるものだけをKとして許容するようにする。必然的にTはobjectになる。
オブジェクト内部は[]で囲み、mapped typeを使って繰り返し処理。
Pはプロパティで各valueの部分にオブジェクトのpropsを変数だから[]で囲みT[P]とすることで、繰り返し分値が展開される。
Pick<T,K>
はこれを内包した記述方法である。
Readonly
回答できた。
interface Todo {
title: string
description: string
}
type MyReadonly<T> = {
readonly [P in keyof T]: T[P];
};
const todo: MyReadonly<Todo> = {
title: "Hey",
description: "foobar"
}
todo.title = "Hello" // Error: cannot reassign a readonly property
todo.description = "barFoo" // Error: cannot reassign a readonly property
今回はtype T = Readonly<{~~~~}>
を使わずに実装する。
今回as const
と各propsの前につけるreadonly
が思いついたので、一応どっちがいいか気になって調べた。
↑上記参考資料によると、Readonlyは直下のpropsしか見ない模様。一方でas const では配列のpush なども型エラーにしてくれる模様。
ちなみにreadonly 修飾子を全てつけるのが、Readonly<T>。同等なので、今回はas constは使わない方がいいと判断した。
Tuple to Object
解答できた。
const tuple = ['tesla', 'model 3', 'model X', 'model Y'] as const
// 問題がstringなので一応stringにした。
type TupleToObject<T extends readonly string[]> = {
[V in T[number]]: V;
}
type result = TupleToObject<typeof tuple>
タプルに関する参考資料。
arrayにas constをつけた値をtypeofで出すと下記のようになる。
const tuple = ['tesla', 'model 3', 'model X', 'model Y'] as const
type tuple = typeof tuple // readonly ['tesla', 'model 3', 'model X', 'model Y']
配列のまま型を生成することが可能になる。
加えて、any[][number]
でindexで回すみたいなことが可能になる。
// Tをreadonlyの配列のみを許容するようにする。
type TupleToObject<T extends readonly string[]> = {
[V in T[number]]: V;
}
T[number]でindexしつつ、mapped typeを使って配列の値をVで取り出すことで、objectに変換。
First of Array
回答できた。最初できたと思ってissue見たらconditional typeがなぜ使われているのかがわからなかったため、再度考え以下のように実装。
type arr1 = ["a", "b", "c"];
type arr2 = [3, 2, 1];
type First<T extends unknown[]> = T extends [] ? never : T[0];
type head1 = First<arr1>; // expected to be 'a'
type head2 = First<arr2>; // expected to be 3
const value1: head1 = "a";
const value2: head2 = 3;
console.log(value1);
console.log(value2);
type First<T extends unknown[]> = T extends [] ? never : T[0];
まずGenericsで何でもいいから配列がくるようにして、空配列だったらnever、そうでなければT[0]で配列の先頭の値を型として持たせる。
conditional typeが出てきたので、復習。
例えばこんな感じだったら、T extends U ? nerver : T;
TがUに代入できたら、nerver。そうでなければT。
anyとunknownの違い。
anyは何でもあり。unknownは代入とか参照はできるけど、オブジェクトのプロパティとメソッドの処理ができない。unknownあくまでわからないを明示している
neverは値を持たない型。
値が入った瞬間にコンパイルエラーになる。
Length of Tuple
type tesla = ["tesla", "model 3", "model X", "model Y"];
type spaceX = [
"FALCON 9",
"FALCON HEAVY",
"DRAGON",
"STARSHIP",
"HUMAN SPACEFLIGHT"
];
type Length<T extends unknown[]> = T extends [] ? never : T["length"];
type teslaLength = Length<tesla>; // expected 4
type spaceXLength = Length<spaceX>; // expected 5
T["length"]
配列のlengthを取得することができる。
Exclude
回答できた。意外と簡単。
type HandmadeExclude<T, K> = T extends K ? never : T;
const value: HandmadeExclude<"a" | "b" | "c", "a" | "c"> = "b";
console.log(value);
"a" | "b" | "c"
みたいに複数を取りうる型だったとしてもmapped typeを使う必要はない。
extendsでできる。
一つの型を生成する際には必要ないが、プロパティを生成するしたり反復するような型を定義する際には必要
awaited
これは解答見ないとわからなかった。
こんな感じ?
type Awaited<T> = T extends Promise<infer U> ? U : never;
inferで型を推論させて、Uで取得してセットする。
↓以下みたいな感じでできる。
PromiseにPromiseがラップされていたときに使える。
type MyAwaited<T extends Promise<any>> =
T extends Promise<infer U> ?
U extends Promise<any> ? MyAwaited<U> : U
: never
typeofの挙動で違和感を感じたが、引用で理解した。
注意しなければいけないのは、JavaScript の typeof は、TypeScript のタイプアノテーションで指定した型を返してくれるわけではないということです。 typeof が返す型情報は、JavaScript コードとして実行したときに、実際にその値がどのような型で扱われているかを示すものです。 なので、TypeScript 独自のタイプアノテーション情報(Tuple 型など)が調べられるわけではありません。 TypeScript のタイプアノテーションは、JavaScript へのトランスパイル時に使われる参考情報でしかありません。
IF
回答できた。
type If<C extends true | false, T, F> = C extends true ? T : F;
基本的なconditional typeの問題で簡単だった。
Concat
type Concat<T extends unknown[], K extends unknown[]> = [...T, ...K];
型の内部でスプレッド構文使えるの初めて知った。
includes
type Includes<T extends unknown[], K> = K extends T[number] ? true : false;
type isPillarMen = Includes<["Kars", "Esidisi", "Wamuu", "Santana"], "Dio">;
Push
type Result = Push<[1, 2], "3">;
type Push<T extends unknown[], U> = [...T, U];
この辺は慣れかも、配列に対する処理としてスプレッド構文使えそうかまず考えてみてもいいかも。
複雑な処理はいらない気がする。
Unshift
ここまで来たら簡単。
type Unshift<T extends unknown[], U> = [U, ...T];
pushと順序変えるだけで良いから楽
parameter
type Params<T extends (...args: any) => any> = T extends (
...args: infer U
) => any
? U
: never;
これが一番わからなかった。
↑のリンクを参考にしてもう一回読んで理解する。
Parameters<Type>がそもそもタプルで返してくれることに気づかなかった。