二つの引数T、Kをとり、Kが指定されていればTのそのプロパティを、指定されていなければすべてのTのプロパティを読み取り専用に変換するMyReadonly<T, K>を実装する。
type MyReadonly2<T, K extends keyof T = keyof T> = { readonly [P in K]: T[P] } & Omit<T, K>
「readonlyなKで指定されているプロパティ & Tに含まれるK以外のプロパティ」を目指して作っていきます。
readonlyなKで指定されているプロパティを作るこれが通常のReadonlyの実装です。
type MyReadonly2<T, K extends keyof T> = { readonly [P in T]: T[P] }
このままだとKの値がreadonlyにならないので、mapped typesでぐるぐるするところを変えます。
type MyReadonly2<T, K extends keyof T> = {
- readonly [P in T]: T[P]
+ readonly [P in K]: T[P]
}
これによりKに渡されたプロパティはreadonlyになります。
Tに含まれるK以外のプロパティこれだけだとKに含まれるプロパティしか含まれていません。そのため、Kに渡されなかったプロパティの型を取得する必要があります。これはTの中からKに該当するプロパティを除いたものです。
例えば以下のようなTとKを渡すことを考えます。
// こっちがT
type Music ={
name: string,
artist: string,
releaseYear: number
}
// こっちがK
type ReadonlyRequiredParams = "artist" | "releaseYear"
最終的にMyReadonly2に期待するのは次のような形式なので、name: stringを取り出せれば良いはずです。
type Expected = {
name: string, // TODO: これから取得したい
readonly artist: string, // { readonly [P in K]: T[P] } で表現される
readonly releaseYear: number // { readonly [P in K]: T[P] } で表現される
}
これは昨日出てきたOmit(組み込みの型の方です)を使用して、Omit<T, K>の形式で取り出すことができます。
type Music2 = Omit<Music, ReadonlyRequiredParams>
// type Music2 = { name: string; }
これらをインターセクション型で繋ぎこむと、解答を得ることができます。
type MyReadonly2<T, K extends keyof T> = { readonly [P in K]: T[P] } & Omit<T, K>
Kのデフォルト値を設定する…と思ったらまだエラーが出ています。この型はKを省略可能なのでそこでひっかります。Kは参照されるので何かしらの値を入れておく必要があります。
TypeScriptは型引数にデフォルト値を取ることができます。
https://typescriptbook.jp/reference/generics/default-type-parameter
今回は「Kを指定しなかった場合、すべてのプロパティがreadonlyになる」ので、Kには「Tのプロパティすべて」を設定します。
type MyReadonly2<T,
- K extends keyof T
+ K extends keyof T = keyof T
>
他の人の解答を見ていたら、Omit<T, K>の部分を& Tで繋ぎ込んでいる解答もあったのですが、これではだめでした。
interface Todo1 {
title: string
description?: string
completed: boolean
}
interface Todo2 {
readonly title: string
description?: string
completed: boolean
}
type TodoX = Todo1 & Todo2
const todoX: TodoX = {
title: "JavaScriptを勉強する",
completed: true
}
todoX.title = "TypeScript" // titleはreadonlyではない。
う〜ん、最後のインターセクション型の挙動についてはドキュメントをざっと読んだのですが、期待する記述は見つけられませんでした。readonlyだけならいいんですが、他にも自分が理解できていない部分があると怖いです。