Closed15

type-challengeやっていくぞ①

ShionShion

Hello World

問題

type HelloWorld = any // expected to be string

回答

type HelloWorld = string // expected to be a string

解説

特になし。

ShionShion

Pick

問題

type MyPick<T, K> = any

回答

type MyPick<T, K extends keyof T> = {
  [P in K]: T[P]
}

解説

回答するのに必要な知識は以下。

  1. extends
  2. keyof
  3. 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;
// }
ShionShion

Readonly

問題

type MyReadonly<T> = any

回答

type MyReadonly<T> = {
  readonly [K in keyof T]: T[K]
}

解説

回答するのに必要な知識は以下。

  1. readonly
  2. 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

こちらで解説。

ShionShion

Tuple to Object

問題

type TupleToObject<T extends readonly any[]> = any

回答

type TupleToObject<T extends readonly PropertyKey[]> = {
  [P in T[number]]: P
}

解説

回答するのに必要な知識は以下。

  1. mapped types
  2. indexed access
  3. 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
>;
ShionShion

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

解説

回答するのに必要な知識は以下。

  1. Conditional Types
  2. 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 型を返す。

ShionShion

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;

解説

回答するのに必要な知識は以下。

  1. extends
  2. readonly
  3. infer
extends

こちらで解説。

readonly

こちらで解説。

infer

こちらで解説。

ShionShion

Exclude

問題

type MyExclude<T, U> = any

回答

type MyExclude<T, U> = T extends U ? never : T;

解説

回答するのに必要な知識は以下。

  1. extends
  2. Conditional Type
extends

こちらで解説。

Conditional Type

こちらで解説。

ShionShion

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

解説

回答するのに必要な知識は以下。

  1. PromiseLike
PromiseLike

PromiseLike reference を参照。
※色々解説あったけど、なんかしっくりこなかった。なんかわかったら追記していく

ShionShion

If

問題

type If<C, T, F> = any

回答

type If<C extends boolean, T, F> = C extends true ? T : F;

解説

特になし。

ShionShion

Concat

問題

type Concat<T, U> = any

回答

type Concat<T extends readonly unknown[], U extends readonly unknown[]> = [...T, ...U];

解説

特になし。

ShionShion

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 は見慣れない(ぱっと見ナニコレ)が、下記の記事を見てたらなんとなく理解できた。
https://zenn.dev/yumemi_inc/articles/ff981be751d26c

ShionShion

Push

問題

type Push<T, U> = any

回答

type Push<T extends unknown[], U> = [...T, U]

解説

特になし。

ShionShion

Unshift

問題

type Unshift<T, U> = any

回答

type Unshift<T extends unknown[], U> = [U, ...T];

解説

特になし。

ShionShion

Parameters

問題

type MyParameters<T extends (...args: any[]) => any> = any

回答

type MyParameters<T extends (...args: any[]) => any> = T extends (...args: infer Params) => any ? Params : never;

解説

回答するのに必要な知識は以下。

  1. infer
infer

こちらで解説。

このスクラップは2024/03/17にクローズされました