Gemcook Tech Blog
🧑‍🍳

【TypeScript】オブジェクトのvalueの型を取得したユニオン型を作ってみる

2024/06/12に公開

最近、弊社のフロントエンドチームでは週に1度、ゆるTS勉強会と称して、ゆる〜くTypeScriptの社内勉強会を行っています。
Type Challengeやフロントエンドリーダーから出された問題を僕たちフロントエンドメンバーが解き、みんなに解説する、といったスタイルで実施しています。
https://github.com/type-challenges/type-challenges
問題自体は単純な型操作が大半ですが、それらを解説できるレベルまで理解し、理解が難しいジュニアには簡単に言い換えたり、例を出したり...。と、「ひとに伝えられるようになること」をゴールとして、型に対する理解を深めています。

みんな真剣に取り組みつつ、遊びながらこんな実装もできるよね!という雰囲気で楽しく取り組んでいます✨

今回は、タイトルにあるように「オブジェクトのvalueの型を取得したユニオン型」を作ってみよう、という問題が出て、TSの基本的な内容になりますがとても勉強になったのでここでもまとめてみようと思います🙌🏻

実際にやってみよう

// オブジェクトから、valueの型を取得して作成したUserObjVal型を作成してください。
const user = {
  name: "gemo", // gemo(じぇむお)は弊社のキャラクター名
  age: 7,
  hasChild: false
}

// any部分を編集してください。
// 期待される型としては今回の場合、type UserObjVal = string | number | boolean となります。
type UserObjVal = any

ぜひ一度皆さんでも以下のPlaygroundからこの問題を解いてみてください✏️
https://www.typescriptlang.org/play/?#code/PTAEFcGcFMCdCqGQawyA6GQ5QyHqGQEwyGkGQkQyEAGANwEMAbcaQOwZBo9UCSGQNeVB0-UHUGQMwZAdeUAQjZwfQYBVGLADyAIwBWANTJ0ubQPIMgAwZAqgyARBkBADACgNAYwD2AO0gAXCINABeUAG8NoUPuIBbaAC5QAIgDm0R7vcAaUBBQb18ACkANBkBxBkAAhkApBgBKQHsGQCh9QD5PSkBahkBjhkBLhnRAfoZAH4ZAWBVbUGJvNwB2f3KAC2JIAGF6gEtSABM3ADMyGA0AXy1gwFO5QGg5SmJ9AE9AC4TAMCVaQAvfQDG0+WV1DWDAfHNAUP0lQBiGQGiGakALBjYkwCm5QD21SkAWDUAIFXwjGYAHaFABOFFJMgtQMZYG19J5QAAfezgRwiOAQ0AiXS6UjQaagM6AKwZAFEMgD8GQCaDJpXh8voJflJSADpjMgA

解答例

さて、いかがでしょうか?
僕がこの問題を初見で見た時はさっぱりわかりませんでした!😇

正解は何通りかあると思いますが、僕たちが取り組んだ結果、以下のような解答例になりました。

type UserObjVal = (typeof user)[keyof typeof user]

//  type UserObjVal = string | number | boolean となる。

typeofやらkeyofやら出てきてなんかややこしそうですね。

一つずつ解説していきましょう。
まずは(typeof user)
typeof 〇〇で〇〇に入る変数の型を取得することができます。
変数の型を取得するので〇〇にはは入れられません。
https://typescriptbook.jp/reference/values-types-variables/typeof-operator
今回の場合は、userオブジェクトの型を取得しているので以下のようになります。

// type UserObj = typeof userとした結果の型
type UserObj = {
  name: string;
  age: number;
  hasChild: boolean;
}

続いて[keyof typeof user]
keyof 〇〇で〇〇に入るオブジェクト型からプロパティ名をユニオン型として取得することができます。
〇〇に入るのはあくまでもなので、変数などは入れられません。
https://typescriptbook.jp/reference/type-reuse/keyof-type-operator
そして前述の通り、typeof useruserオブジェクトの型を取得しています。
そのため、ここではuserオブジェクトのプロパティ名をユニオン型として取得している、ということになります。

// type UserObjKey = keyof typeof userとした結果の型
// userオブジェクト型からプロパティ名がユニオン型として返される。
type UserObjKey = "name" | "age" | "hasChild"

最後に(typeof user)[keyof typeof user]この形です。
TypeScriptではオブジェクト型["プロパティ名"]とすることで、プロパティ名にあたるvalueの型にアクセスすることができます。Lookup型というやつですね。

例えば、userオブジェクトのnameプロパティの型をとりたい場合は...。

// type UserName = (typeof user)["name"]とした結果の型
type UserName = string

というわけですね。

では、今回の問題を改めてみていきましょう。
実は今回もLookup型によってvalueの型をとってきているだけです。つまり、

(typeof user)[keyof typeof user]

("userオブジェクト型"の)["userオブジェクト型のプロパティ"にアクセスして、そのvalueの型を返す]

という意味になり、value部分の型を取ってきているわけですね。

以上をまとめると今回の問題の解答としては、以下のようになります。

// userオブジェクト型
type UserObj = typeof user
// つまり...。 type UserObj = {name: string, age: number, hasChild: boolean}

// userオブジェクトのプロパティのユニオン型
type UserObjKey = keyof typeof user
// つまり...。 type UserObjKey = keyof UserObj
// (type UserObjKey = "name" | "age" | "hasChild")

// userオブジェクトのValueの型を取得したユニオン型
type UserObjVal = (typeof user)[keyof typeof user]
// つまり...。 type UserObjVal = UserObj[UserObjKey]
// (type UserObjVal = string | number | boolean)

これでオブジェクト型のvalueの型を取得したユニオン型を作ることができました🎉

別の解答案

ちなみにジェネリクスを使って遊びながら他の解答も出していたので気になる方は参考までにどうぞ😎

別解①、別解②

別解①

type ValueOf<T> = T[keyof T]
type UserVal = ValueOf<typeof user>

//  type UserVal = string | number | boolean

ジェネリクス<T>を受け取り、Lookup型で<T>のプロパティにアクセスし、そのvalueをユニオン型として取得しています。

別解②

別解①だと、ジェネリクス<T>にオブジェクト以外のものも受けつけるようになっています。
そのため、オブジェクトだけに絞った型を作ってみようということで以下のような解答も出ました。

type ValueOf<T extends Record<string | number | symbol, unknown>> = T[keyof T]
type UserVal = ValueOf<typeof user>

//  type UserVal = string | number | boolean

うん、難しい!😇
ゆるTS勉強会で解説してもらったので理解できるようにはなっていますが、それぞれを詳細に解説しようと思うととっても長くなるので、また別の機会に...🫢

最後に

いかがでしたでしょうか?
僕としては、初見では難しかったですが、一度理解してしまえば次からはしっかり理解できるようになっており、とても良い勉強になりました。

TypeScriptをしっかり理解しながら実装できると型安全に開発できるため、バグのリスクも減り開発者体験も向上します。
僕もまだまだ勉強中ですが、これから継続的にTypeScriptを学習し、TSマスターになれるよう頑張ります🔥

TypeScriptを勉強中の方はぜひ一緒に頑張りましょう💪🏻

Gemcook Tech Blog
Gemcook Tech Blog

Discussion