TypeScript 練習 オブジェクトの文字列の値をstringのリテラルとして取得する

以下のようなオブジェクトがあるとします。

const actions = {
    user: {
        update: "USER_UPDATE",
        remove: "USER_REMOVE",
        admin: {
            update: "USER_ADMIN_UPDATE"
        },
        invalid:9999
    },
    mail: {
        create: 'MAIL_CREATE'
    },
    reset: "RESET_ALL"
}

このオブジェクトから、以下のような型を取得することを目標にします。

type MyType = "USER_UPDATE" |  "USER_REMOVE" | "USER_ADMIN_UPDATE" | 'MAIL_CREATE' | "RESET_ALL"

どこかで見覚えがありますが、そう、Redux の Action です。

まずは actions から型を取得するために const アサーションをつけます。

const actions = {
    user: {
        update: "USER_UPDATE",
        remove: "USER_REMOVE",
        admin: {
            update: "USER_ADMIN_UPDATE"
        },
        invalid:9999
    },
    mail: {
        create: 'MAIL_CREATE'
    },
    reset: "RESET_ALL"
} as const

これで typeof を使うことで actions を型にできます。

type Actions = typeof actions

続いて、オブジェクト型のstringである文字列だけを再帰的に取得してUNIONにする型をつくります。

type MakeActionsUnion<T extends Record<string, unknown>> = {
    [K in keyof T]: T[K] extends string ? T[K] : T[K] extends Record<string, unknown> ? T2<T[K]> : never
}[keyof T]

あとはこのT1を使って、Actions から文字列のUNIONを作り出します。

type ActionsUnion = MakeActionsUnion<Actions>

実際に想定した動きになるか見ます。

const a: ActionsUnion = "USER_UPDATE" //OK
const b: ActionsUnion = "USER_ADMIN_UPDATE" //OK
const c: ActionsUnion = 9999 //NG
const d: ActionsUnion = "MAIL_CREATE" //OK
const e: ActionsUnion = "RESET_ALL" //OK
const f: ActionsUnion = "hogehoge" //NG