準同型なマップ型がどのように修飾子を保持するか、そして as 節がどのようにキーを名前変更・フィルタリングするかを理解する。
このレッスンを Kodokon で開くマップ型は、あるキーの集合を反復処理して新しいオブジェクト型を生成します。[K in keyof T] の形は準同型と呼ばれます: コンパイラは構造が T に由来することを知っており、各元プロパティの readonly および ? 修飾子を自動的にコピーします。+ と - の接頭辞を使うと、これらの修飾子を明示的に追加・削除できます: これこそが Partial、Required、Readonly の背後にある内部の仕組みそのものです。
type Mutable<T> = {
-readonly [K in keyof T]-?: T[K];
};
interface Config {
readonly host?: string;
readonly port?: number;
}
type Draft = Mutable<Config>;
// { host: string; port: number }TypeScript 4.1 以降、as 節はキーの再マッピングを可能にします: 各キーはその場で名前変更でき、典型的にはテンプレートリテラル型を通じて行います。そして、この節が never を生成すると、そのキーは単に結果から削除されます: これがプロパティフィルタリングの公式な仕組みです。例に出てくる & string に注目してください: keyof T には symbol や number のキーが含まれることがあり、それらはテンプレートリテラルと互換性がありません。
type Getters<T> = {
[K in keyof T & string as `get${Capitalize<K>}`]:
() => T[K];
};
interface User { name: string; age: number }
type UserGetters = Getters<User>;
// { getName: () => string; getAge: () => number }
type OnlyMethods<T> = {
[K in keyof T as
T[K] extends (...args: never[]) => unknown
? K : never]: T[K];
};見落とされがちな仕様上の細かな点があります: 配列やタプルに適用した場合、準同型なマップ型はキー 0、1、length を持つオブジェクトを生成しません。コンパイラは型の性質を保持します: 要素を変換しつつ、配列やタプルの形を保つのです。これこそが Readonly<[number, string]> がタプルのまま残る理由です。この保存性は、マップ型が準同型でなくなった途端に失われます。例えば、K がキーの任意のユニオンである [P in K] のような場合です。
type Immutable<T> = {
readonly [K in keyof T]: T[K];
};
type Pair = Immutable<[number, string]>;
// readonly [number, string]: the tuple survives
type List = Immutable<string[]>;
// readonly string[]as 節があるキーに対して never を生成すると何が起こりますか?{ readonly [K in keyof T]: T[K] } をタプル [number, string] に適用すると何が生成されますか?-? 修飾子の効果は何ですか?