お題
K
が指定されている場合、オブジェクト型T
の中のK
のプロパティのみを読み取り専用にし、K
が指定されていない場合、通常のReadonly<T>
と同様に、すべてのプロパティを読み取り専用にする型MyReadonly2
を実装する。
やりたいこと
interface Todo {
title: string
description?: string
completed: boolean
};
type Expected = MyReadonly2<Todo, 'title' | 'description'>;
interface Expected {
readonly title: string
readonly description?: string
completed: boolean
};
解答
type MyReadonly2<T, K extends keyof T = keyof T> = {
readonly [P in K]: T[P]
} & {
[P in keyof T as P extends K ? never : P]: T[P]
};
解説
処理の流れ
-
<T, K extends keyof T = keyof T>
-
K
に入る値を、T
のプロパティのみに制約 -
default valueを使用し、
K
が指定されない場合はkeyof T
が割り当てられる
(K
が指定されない場合に、すべてのプロパティが読み取り専用になる)
-
-
{ readonly [P in K]: T[P] }
Mapped Typesを使用し、K
のプロパティのみの読み取り専用オブジェクトを作成。 -
{ [P in keyof T as P extends K ? never : P]: T[P] }
Mapped Typesとas
を使用し、元の型を保持したK
以外のプロパティのオブジェクトを作成。 -
{...} & {...}
インターセクション型(&)を使用し、オブジェクトを合成する。
default valueとは...
引数がundefined
のとき、代わりの値を指定できる
JavaScriptは引数を省略すると、undefined
が返される。
// 書き方
function 関数名(引数 = デフォルト値) {}
// 型注釈を入れる場合
function 関数名(引数: 型 = デフォルト値) {}
インターセクション型(&)とは...
合成したいオブジェクト同士を&
で列挙することで、インターセクション型を作る。
type A = { name: string };
type B = { age: number };
type AB = A & B; // { name: string; age: number; }
プリミティブ型のインターセクション型
never
が返される。
type Never = string & number; // never
keyof演算子とは...
Mapped Typesとは...
Mapped Typesにおける「as」とは...
参考記事
default value
インターセクション型
今回の問題