どの言語でも体感しているのですが、Generics がしっかり書けると、コードが簡潔にそして何よりカッコよく書ける!
まず、特定の Object からid
を取り除いた型を用意したい場合、Generics を使わないと 2 つのインターフェイスを用意しないといけない。※1
id
がないだけなのに冗長的でかなり残念です。このようなコードを書くとレビュー指摘は必至でしょう。
interface User {
id: string
sei: string
mei: string
age: number
address: string
}
interface UserOmitId {
sei: string
mei: string
age: number
address: string
}
※1 Type を使っても完結に書けます。
type UserOmitId = {
sei: string
mei: string
age: number
address: string
}
type HasId = { id: string }
type User = UserOmitId & HasId
Omit で書くと
TypeScript のライブラリの
utility-types
の 1 機能の Omit
を使うと以下のように書ける。
type OmitId<T> = Omit<T, "id">
型 T の中から、指定したキー名のプロパティを除外した新しい型を返す。
Omit<User, "id">
これと Generics を組み合わせると
id
の要素を削ったオブジェクトを返却する関数を定義することができる。
User 型に依存しないで、かなり汎用的に使えますね。
function removeId<T extends HasId>(obj: T): OmitId<T> {
const result = { ...obj }
delete result.id // IDキーを除外する
return result
}
試しに、Jest でテストコードを書くとこのようになります。
test("OmitId test", () => {
const user: User = {
id: "1",
sei: "Tanaka",
mei: "Taro",
age: 30,
address: "東京都新宿区"
}
const expectedUser = {
sei: "Tanaka",
mei: "Taro",
age: 30,
address: "東京都新宿区"
}
const result = removeId(user)
expect(result).toEqual(expectedUser)
})
ちょっと応用
上の User
は、 sei
mei
を結合して name
にした型を返却する場合は、
type UserName = { name: string }
type UserSeiMei = { sei: string; mei: string }
※今回は Type で型を定義しましたが、Interface でも可能です。
function concatName<T extends UserSeiMei>(obj: T): T & UserName {
const name = `${obj.sei} ${obj.mei}`
const result = { ...obj }
delete result.sei
delete result.mei
return { ...result, name }
}
このように sei
mei
のキーから値を取り出して結合して、name
を作成して、sei
mei
のキーを削除
test("concat user name", () => {
const user: User = {
id: "1",
sei: "Tanaka",
mei: "Taro",
age: 30,
address: "東京都新宿区"
}
const expectedUser = {
id: "1",
name: "Tanaka Taro",
age: 30,
address: "東京都新宿区"
}
const result = concatName(user)
expect(result).toEqual(expectedUser)
})
まとめ
今回は id
を除外 & 姓名を結合するような、簡単な例ではありましたが、活用すると冗長になりがちなコードを簡潔に書けますね。
カッコ良いコードを書けるように学習を進めたいと思います。