0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

type-challengesの解説(easy編)

Last updated at Posted at 2023-03-15

概要

image.png
type-challengesを解き、考える上でポイントだった部分を書き残していきます。
easy編をすべて解けばTypeScriptの基本的な構文については理解したと言えるレベルになれると思います。
easy編はたった14問なのでTypeScriptを勉強している方はチャレンジすると良いと思います。

type-challengesとは

https://github.com/type-challenges/type-challenges
上記リポジトリにある、TypeScriptの型定義に関する問題集です。
全部で150問あり、easy、medium、hard、extremeの4レベルに分けられています。
日本語の問題もあり、解答例もgithubのissueで利用することができるます。
easyもそこそこ難しく、TypeScriptの型定義で使われる文法すべてを最低限知っておく必要があります。
TypeScriptの学習に関してはMicrosoftのTypeScript Handbookが充実しています。(一通りこちらのドキュメントを読んでから問題に取り組むことを推奨します。)

環境構築

  • pnpmのインストール
  • pnpm install && pnpm generate
  • 追加ライブラリのインストール(typescriptを入れてtscコマンドを使えるようにすると、テストケースをパスしたかチェックできます)
  • eslintとprettierの設定

各問題の解説

0004

Indexed Access TypesMapped Typesを理解できれば解けるはずです。
[k in K]Kとしてユニオン型を使えばそのユニオン型に含まれるkeyを繰り返す動きになります。(Indexed Access Types)
T[k]でオブジェクト型Tkフィールドの型を取り出すことができます。(Mapped Access Types)
genericsでKの型をTのkeyに制限してやる(K extends keyof T)ことのがポイントです。(そうしないとエラーケースをパスしません。)

正解例
type MyPick<T, K extends keyof T> = {
  [k in K]: T[k]
}

00007

オブジェクト型のプロパティの前にreadonlyを付けるとそのプロパティをreadonly型にできることを使います。
Handbookのこのあたりを理解すれば解けます。
Mapping Modiffiersを使えばオブジェクトのプロパティにはreadonlyか否か、optionalか否かの属性を付与/剥奪することができます。

Mapping Modifierの使用例
// readonly付与
type MakeReadonly<Type> = {
  readonly [Property in keyof Type]: Type[Property];
};
// readonly剥奪
type MakeUnReadonly<Type> = {
  -readonly [Property in keyof Type]: Type[Property];
};
// optional付与
type MakeOptional<Type> = {
  [Property in keyof Type]?: Type[Property];
};
// optional剥奪
type MakeUnOptional<Type> = {
  readonly [Property in keyof Type]-?: Type[Property];
};
正解例
type MyReadonly<T> = {
  readonly [K in keyof T]: T[K]
}

00011

ここまでの問題を理解できていれば解けるはずです。

正解例
type TupleToObject<T extends readonly (string | number | symbol)[]> = {
  [k in T[number]]: k;
};

00014

[]で要素数0の配列の型を表現できるのがポイント。要素数が0か否かで場合分けして返す。

間違い例1
type First<T extends any[]> = T[0];
Expect<Equal<First<[]>, never>> // undefinedになってしまう
間違い例2
type First<T extends any[]> = T[0] extends undefined ? never : T[0];
Expect<Equal<First<[undefined]>, undefined>> // neverになってしまう
正解例
type First<T extends any[]> = T extends [] ? never : T[0];

00018

Indexed Access Typesでlengthプロパティを取得すれば良い。genericsで受け取る型が何らかの配列であるという制限をするために、extendsで配列に限定することがポイント

間違い例
type Length<T> = T['length'];

// @ts-expect-error
Length<5> // errorがあがらない

genericsの入力型に配列であるという条件をつけてやればよい

正解例
type Length<T extends readonly any[]> = T['length'];

00043

Uであればneverを、UでなければTを返せばOK

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

00189

Promiseが自分自身を内包し得る型であるため、再帰的な型定義を考えたいです。ジェネリクスで受け取った型TがPromiseならばその中身の型Uに対して再帰的に自身を呼び出し、Non-Promiseならばそのまま返すような型定義RAwaitedを作るのがポイントです。
このままだとNon-Promiseをそのまま渡される場合のエラーケースに対応できないのでMyAwaitedの中でRwaitedを使うようにし、MyAwaitedでは条件型を使ってエラーを起こすようにしてやればよいです。
実際テストケースにPromise<Promise<Promise<T>>>みたいなパターンも含まれているのでこれに対応する必要があります。

正解例
type RAwaited<T> = T extends PromiseLike<infer U> ? RAwaited<U> : T;
type MyAwaited<T extends PromiseLike<any>> = RAwaited<T>;

00268

true型、false型を使って条件分岐するだけ

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

00533

型におけるスプレッド構文を使うことがポイント
参考:https://zenn.dev/cookiegg/articles/typescript-spread-type
(1 | 2)[][1, 2]の型は違うことを理解する必要がある

間違い例
// 以下だとConcat<[1, 2], []>の結果が(1 | 2)[]となってしまうのでNG
type Concat<T extends any[], U extends any[]> = (T[number] | U[number])[];
正解例
type Concat<T extends any[], U extends any[]> = [...T, ...U];

00898

easyレベルの問題の中で最高難易度の問題。
ポイントは2つ

  1. 型の一致判定をどうするか?
  2. 配列型の一つ一つの要素の型との一致比較をどうするか

1についてはextendsは使えない。extendsはあくまでextends(拡張)した型であることを判定するものであるため。
そこで少しずるいが、type-challengesのテストケースを見るとEqual<A,B>なる型が用意されてあり、これを利用すれば型の一致判定を行うことができる。もちろん、自分で同じような型を実装するのもあり。type-challengesのEqual型の実装を見て理解するのも勉強になると思うのでぜひ。

2については00189でやったような再帰的な型定義を使うことで解決できる。

上記ポイントに加えextends構文をきっちり理解し、inferを使えるかもポイント。extends構文とinferの使い方についてはこちらの記事が分かりやすかった。TypeScriptのextendsってなんなん?

またタプル型の残余展開を利用し、タプル型の要素を1番目とそれ以外に分ける方法も知っておく必要がある。残余展開についてはHandbook
参照

間違い例
type Includes<T extends readonly any[], U> = U extends T[number] ? true : false;
正解例
type Includes<T extends readonly any[], U> = T extends [infer T1, ...infer Ts]
  ? Equal<T1, U> extends true
    ? true
    : Includes<Ts, U>
  : false;

03057

簡単。タプル型の残余展開を知っていれば解けるはず。

正解例
type Push<T extends any[], U> = [...T, U];

03060

簡単。03057と同様。

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

03312

関数Tに何個存在するか分からない引数をまとめてinferする方法がわかれば解ける。正解を言ってしまうと、適当に引数に名前をつけて(正解例ではarg)、その引数を残余展開すればよい。...を付けるのが型名ではなく変数名の前であることがポイントで、これによりTは引数の方の配列になる。

正解例
type MyParameters<T> = T extends (...args: infer I) => any ? I : never;
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?