TS: Komposisi melalui enum

Feb 13 2023
Pada artikel ini, saya akan menunjukkan varian komposisi tipe melalui deklarasi awal bidang melalui enum
Dalam varian sederhana mengetik melalui enum, tipe kami terlihat seperti ini: Keuntungan dari pendekatan ini adalah kami membuat tipe berdasarkan daftar bidang yang sudah dijelaskan, sehingga membatasi kemungkinan kesalahan bidang. Dalam nada ini, enum menjadi abstraksi tambahan yang memungkinkan Anda untuk berpikir bukan dalam tipe, tetapi dalam subset bidang.

Dalam varian sederhana mengetik melalui enum, tipe kami terlihat seperti ini:

export enum TypeFields{
  id = 'id',
  title = 'title'
  ...
}
            //type Record<K extends string, T> = { [P in K]: T }
export type Type = Record<TypeFields,string>

// converted to: 
//
// type Type = {
//     id: string;
//     title: string;
// }

Keuntungan dari pendekatan ini adalah kami membuat tipe berdasarkan daftar bidang yang sudah dijelaskan, sehingga membatasi kemungkinan kesalahan bidang. Dalam nada ini, enum menjadi abstraksi tambahan yang memungkinkan Anda untuk berpikir bukan dalam tipe, tetapi dalam subset bidang.

Namun, nuansa komposisi tersebut adalah kami tidak memiliki alat untuk menyusun beberapa enum menjadi satu jenis umum.

Idenya adalah untuk merancang tipe utilitas yang akan memungkinkan kita untuk menggabungkan pasangan [enum,type][] menjadi satu tipe di mana setiap bidang akan memiliki tipe yang ditetapkan ke enum dan bukan ke satu bidang.

Untuk membuat jenis utilitas seperti itu, Anda perlu memahami 2 prinsip penting:

Prinsip membangun jenis utilitas rekursif.

Prinsip penghematan perantara dari hasil dalam jenis utilitas.

Mari mulai membuat tipe kita dengan bentuk interaksi dengannya:

Seperti yang kami definisikan sebelumnya, kami ingin dapat menggabungkan beberapa enum dan memberi masing-masing tipe

enum A {
    a = 'a'
}

enum B {
    b = 'b'
}

enum C {
    c = 'c'
}

type MergedType = MergeEnums<[
    [A,string],
    [B,boolean],
    [C,number],
]>

// expected result of MergeEnums utility type: 
//
// type MergedType{
//   [A.a]: string,
//   [B.b]: boolean,
//   [C.c]: number,
// }
export type MergeEnums<Entries extends [keyof any,unknown][]>

Algoritmanya adalah sebagai berikut - kami mengambil larik entri kami, membaginya menjadi dua entitas - pasangan pertama dan semua pasangan lainnya, setelah itu kami mengubah pasangan pertama [Enum, T] menjadi Record<Enum,T> dan menulis ini ketik ke akumulator, ulangi algoritme selama ada pasangan di daftar.

Berikut adalah contoh implementasi:


type MergeEnums<
  Entries extends [keyof any,unknown][], // enries array to process
  Acc extends object = {} // accumulator 
> = 
    Entries extends [infer CurrentEntry, ...infer ShiftedEntries] // shift first pair from entries array
    ? ShiftedEntries extends [keyof any, unknown][] // type compare check
    ? CurrentEntry extends [infer Enum,infer Type] // get enum and type for it from shifted entries pair
    ? Enum extends keyof any // type compare check
    ? MergeEnums<ShiftedEntries, Acc & Record<Enum, Type>> // new iteration of MergeEnums with updated accumulator
    // recursion exit
    : Acc 
    : Acc
    : Acc
    : Acc
enum A {
    a = 'a'
}

enum B {
    b = 'b'
}

enum C {
    c = 'c'
}

type Test = MergeEnums<[
    [A,string],
    [B,boolean],
    [C,number]
]>

const test: Test = {
    [A.a]: 'true',
    [B.b]: false,
    [C.c]: 1
}

Mengapa ini berguna?

Manfaat dari utilitas tipe seperti itu adalah kita dapat membagi tipe yang dihasilkan menjadi blok bidang logis melalui lapisan enum

enum UserProfileSomeRequiredFields{
    id = 'id',
    ...
}

enum UserProfileSomeOptionalFields{
    status = 'status',
    lang = 'lang', 
    ...
}

enum UserProfileSomeResources {
    somePageResource = 'somePageResource',
    someOtherResource = 'someOtherResource'
    ...
}

type SomeResponse = MergeEnums<[
    [UserProfileSomeRequiredFields,number],
    [UserProfileSomeOptionalFields,string],
    [UserProfileSomeResources,boolean]
]>