type-challengeやっていくぞ①
まずはお試し+初級から
Hello World
問題
type HelloWorld = any // expected to be string
回答
type HelloWorld = string // expected to be a string
解説
特になし。
Pick
問題
type MyPick<T, K> = any
回答
type MyPick<T, K extends keyof T> = {
[P in K]: T[P]
}
解説
回答するのに必要な知識は以下。
- extends
- keyof
- mapped types
extends
型継承や型制限(今回はこれ)で使用。
TypeScriptに「KはTの部分型である」ことを伝えている。
interface Lengthwise {
length: number;
}
function loggingIdentity<Type extends Lengthwise>(arg: Type): Type {
console.log(arg.length); // Now we know it has a .length property, so no more error
return arg;
}
loggingIdentity({ length: 10, value: 3 }); // work
loggingIdentity(3); // error
keyof
オブジェクトの型からプロパティ名を型として返す型演算子。
type Book = {
title: string;
price: number;
rating: number;
};
type BookKey = keyof Book; // "title" | "price" | "rating"
mapped types
ユニオンのキーを反復して型を作成する。
type SystemSupportLanguage = "en" | "fr" | "it" | "es";
type Butterfly = {
[key in SystemSupportLanguage]: string;
};
// 上は次と同じ意味になる
// type Butterfly = {
// en: string;
// fr: string;
// it: string;
// es: string;
// }
Readonly
問題
type MyReadonly<T> = any
回答
type MyReadonly<T> = {
readonly [K in keyof T]: T[K]
}
解説
回答するのに必要な知識は以下。
- readonly
- mapped type
readonly
インターフェース上の個々のプロパティをreadonlyとしてマークすることで、読み取り専用にできる。
interface User {
readonly name: string;
readonly age: number
}
const stephen: User = {
name: "stephen curry",
age: 32
}
stephen.age = 33; // ERROR: Cannot assign to 'age' because it is a read-only property.
mapped types
こちらで解説。
Tuple to Object
問題
type TupleToObject<T extends readonly any[]> = any
回答
type TupleToObject<T extends readonly PropertyKey[]> = {
[P in T[number]]: P
}
解説
回答するのに必要な知識は以下。
- mapped types
- indexed access
- PropertyKey(built-in)
mapped types
こちらで解説。
indexed access
公式の indexed access を確認する。
type Person = { age: number; name: string; alive: boolean };
type AliveOrName = "alive" | "name";
type Age1 = Person["age"]; // type Age1 = number
type Age2 = Person["age" | "name"]; // type Age2 = string | number
type Age3 = Person[keyof Person]; // type Age3 = string | number | boolean
type Age4 = Person[AliveOrName]; // type Age4 = string | boolean
また、number
を使って配列の要素の型を取得することができる。
const MyArray = [
{ name: "Alice", age: 15 },
{ name: "Bob", age: 23 },
{ name: "Eve", age: 38 },
];
type Person = typeof MyArray[number];
// type Person = {
// name: string;
// age: number;
// }
type Age = typeof MyArray[number]["age"]; // type Age = number
PropertyKey
TypeScript組み込みのグローバルタイプで string | number | symbol
を意味する。
type Example = PropertyKey;
// type Example = string | number | symbol
想定しうる全てのキーをもつレコードタイプを作りたい状況で有用。
type RecordWithAllKeys = Record<
PropertyKey,
unknown
>;
First of Array
問題
type First<T extends any[]> = any
回答
//answer1
type First<T extends any[]> = T extends [] ? never : T[0]
//answer2
type First<T extends any[]> = T['length'] extends 0 ? never : T[0]
//answer3
type First<T extends any[]> = T extends [infer P, ...any[]] ? P : never
解説
回答するのに必要な知識は以下。
- Conditional Types
- infer
Conditional Types
公式の Conditional Types を確認する。
extends の左側の型が右側の型に代入可能な場合、前者の型が得られ、そうでない場合後者の型が得られる。
interface Animal {
live(): void;
}
interface Dog extends Animal {
woof(): void;
}
type Example1 = Dog extends Animal ? number : string;
// type Example1 = number
type Example2 = RegExp extends Animal ? number : string;
// type Example2 = string
infer
Conditional Typesの中で使われる型演算子で、条件分岐で推論された型を指すときに用いることができる。
まず以下のコードがあるとする。
type Flatten<T> = T extends any[] ? T[number] : T;
type Str = Flatten<string[]>;
// type Str = string
type Num = Flatten<number>;
// type Num = number
これを infer
で書き換えると次のようになる。
infer Item
と記述したら、Item型を型情報に含めることができる。
type Flatten<T> = T extends Array<infer Item> ? Item : T;
type Str = Flatten<string[]>;
// type Str = string
type Num = Flatten<number>;
// type Num = number
別の例も見てみる。
type GetReturnType<Type> = Type extends (...args: never[]) => infer Return
? Return
: never;
type ReturnNumFunc = () => number
type Num = GetReturnType<ReturnNumFunc>;
// type Num = number
Type
の型は ReturnNumFunc
であり、(...args: never[]) => infer Return
を満たす。
(...args: never[]) => infer Return
// ↓
() => number
最後に infer の回答を見てみる。
type First<T extends any[]> = T extends [infer P, ...any[]] ? P : never
この型エイリアスは、与えられた配列の最初の要素を取得するものである。
[infer P, ...any[]]
は、T配列の 要素が少なくとも1つある場合 を示している。
要素が1つ以上ある場合に、最初の要素の型を P として推論する。
配列が空の場合は、never 型を返す。
Length of Tuple
問題
type Length<T> = any
回答
// answer 1
type Length<T extends readonly string[]> = T["length"]
// answer 2 (ifer ver)
type Length<T extends readonly string[]> = T extends { length: infer L } ? L : never;
解説
回答するのに必要な知識は以下。
- extends
- readonly
- infer
extends
こちらで解説。
readonly
こちらで解説。
infer
こちらで解説。
Awaited
問題
type MyAwaited<T> = any
回答
type MyAwaited<T extends PromiseLike<any | PromiseLike<any>>> =
T extends PromiseLike<infer V>
? V extends PromiseLike<any>
? MyAwaited<V>
: V
: never
解説
回答するのに必要な知識は以下。
- PromiseLike
PromiseLike
PromiseLike reference を参照。
※色々解説あったけど、なんかしっくりこなかった。なんかわかったら追記していく
If
問題
type If<C, T, F> = any
回答
type If<C extends boolean, T, F> = C extends true ? T : F;
解説
特になし。
Concat
問題
type Concat<T, U> = any
回答
type Concat<T extends readonly unknown[], U extends readonly unknown[]> = [...T, ...U];
解説
特になし。
Includes
問題
type Includes<T extends readonly any[], U> = any
回答
export type IsEqual<X, Y> =
(<T>() => T extends X ? 1 : 2) extends
(<T>() => T extends Y ? 1 : 2) ? true : false;
type Includes<T extends readonly unknown[], U> =
T extends [infer First, ...infer Rest]
? IsEqual<First, U> extends true ? true : Includes<Rest, U>
: false;
解説
<T>() => T extends X ? 1 : 2
は見慣れない(ぱっと見ナニコレ)が、下記の記事を見てたらなんとなく理解できた。
Push
問題
type Push<T, U> = any
回答
type Push<T extends unknown[], U> = [...T, U]
解説
特になし。
Unshift
問題
type Unshift<T, U> = any
回答
type Unshift<T extends unknown[], U> = [U, ...T];
解説
特になし。