【TypeScript】オブジェクトのvalueの型を取得したユニオン型を作ってみる
最近、弊社のフロントエンドチームでは週に1度、ゆるTS勉強会と称して、ゆる〜くTypeScriptの社内勉強会を行っています。
Type Challengeやフロントエンドリーダーから出された問題を僕たちフロントエンドメンバーが解き、みんなに解説する、といったスタイルで実施しています。
問題自体は単純な型操作が大半ですが、それらを解説できるレベルまで理解し、理解が難しいジュニアには簡単に言い換えたり、例を出したり...。と、「ひとに伝えられるようになること」をゴールとして、型に対する理解を深めています。
みんな真剣に取り組みつつ、遊びながらこんな実装もできるよね!という雰囲気で楽しく取り組んでいます✨
今回は、タイトルにあるように「オブジェクトのvalue
の型を取得したユニオン型」を作ってみよう、という問題が出て、TSの基本的な内容になりますがとても勉強になったのでここでもまとめてみようと思います🙌🏻
実際にやってみよう
// オブジェクトから、valueの型を取得して作成したUserObjVal型を作成してください。
const user = {
name: "gemo", // gemo(じぇむお)は弊社のキャラクター名
age: 7,
hasChild: false
}
// any部分を編集してください。
// 期待される型としては今回の場合、type UserObjVal = string | number | boolean となります。
type UserObjVal = any
ぜひ一度皆さんでも以下のPlaygroundからこの問題を解いてみてください✏️
解答例
さて、いかがでしょうか?
僕がこの問題を初見で見た時はさっぱりわかりませんでした!😇
正解は何通りかあると思いますが、僕たちが取り組んだ結果、以下のような解答例になりました。
type UserObjVal = (typeof user)[keyof typeof user]
// type UserObjVal = string | number | boolean となる。
typeof
やらkeyof
やら出てきてなんかややこしそうですね。
一つずつ解説していきましょう。
まずは(typeof user)
。
typeof 〇〇
で〇〇に入る変数の型を取得することができます。
変数の型を取得するので〇〇には型は入れられません。
今回の場合は、user
オブジェクトの型を取得しているので以下のようになります。
// type UserObj = typeof userとした結果の型
type UserObj = {
name: string;
age: number;
hasChild: boolean;
}
続いて[keyof typeof user]
。
keyof 〇〇
で〇〇に入るオブジェクト型からプロパティ名をユニオン型として取得することができます。
〇〇に入るのはあくまでも型なので、変数などは入れられません。
そして前述の通り、typeof user
でuser
オブジェクトの型を取得しています。
そのため、ここでは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を勉強中の方はぜひ一緒に頑張りましょう💪🏻
Discussion