Logo 空の箱
Table of Contents
index

extends

この場合のextendsの役割は継承ではなく、制約です。

例えば以下のようなイメージ。

type StringOrNumber<T extends string | number> = T
type A = StringOrNumber<string>
type B = StringOrNumber<number>
// Type 'boolean' does not satisfy the constraint 'string | number'
type C = StringOrNumber<boolean>

エラーメッセージの指摘がまさにそうですよね。Tthe constraint 'string | number'です。

次はオブジェクト型の場合を見てみます。

type User = {
id: number;
name: string;
};
const getUserName = <T extends User>(arg: T): string => arg.name;
getUserName({ id: 123, name: 'aaa' });
getUserName({ id: 123, name: 'aaa', email: 'aaaaaa' });
// Argument of type '{ id: number; }' is not assignable to parameter of type 'User'.
// Property 'name' is missing in type '{ id: number; }' but required in type 'User'
getUserName({ id: 123 });

こちらも同じくtype 'User'では'name'が必須ですという旨のことを言われています。

つまり、K extends Tとは少なくとも、KはTのプロパティ・型を持ってないとダメということになります。

keyof

こちらは比較的簡単な話で、keyofはオブジェクトの型からプロパティ名を型として返す型演算子です。

type User = {
id: number;
name: string;
};
// このコードはどちらも同じ意味
type UserKey = keyof User; // "id" | "name"
const id: UserKey = "id"
const useName: UserKey = "name"
// Type '"age"' is not assignable to type 'keyof User'.
const age: UserKey = "age"

<T, K extends keyof T> を理解する

例えばTが先ほどのUserだったとします。そのとき、keyof User"id" | "name"となります。

かつ、extendsをふまえるとK extends "id" | "name"ということになります。

つまりK"id","name", "id" | "name"のいずれかであれば取ることができますが、"age""id" | "age"をとることはできません。

で、なにが便利なの?

それを知るにはtype-challengesのこの問題を解いてみましょう。

この問題を解くためには、extends, keyof, Mapped Typesの3つの知識を組み合わせる必要があります。

組み込みの型ユーティリティPick<T, K>を使用せず、TからKのプロパティを抽出する型を実装します。

interface Todo {
title: string
description: string
completed: boolean
}
type TodoPreview = MyPick<Todo, 'title' | 'completed'>
const todo: TodoPreview = {
title: 'Clean room',
completed: false,
}

ここで、MyPickをこのように定義することができます。

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

KTのいずれかのプロパティとなり、そのプロパティをkeyとして取り出します。

その上で、T[key]よりプロパティkeyに対応する型をセットします。

結果として、'title' | 'completed'のようにPickしたいプロパティを指定すれば、Pickと同等の結果を得られるというわけです。

おわりに

type-challengesの初級のしょっぱなの問題がこれだったのですが、正直「むずすぎん…?」て感じでした。

いくつかの基本を組み合わせて解かないとダメなので、これ完全に応用じゃん…てなりました。

ただしっかり言語化していくと、とても良い勉強にはなったので良問でした👍


この記事は当初Zennで公開したものを個人ブログ開設にあたって移植したものです。

https://zenn.dev/yskn_sid25/articles/da0547f3128308