Mudahkan Perkerjaanmu dengan Beberapa Tips Typescript Berikut

May 24, 2021 09:58 · 541 words · 3 minute read #types #typescript

Mudahkan Perkerjaanmu dengan Beberapa Tips Typescript Berikut
Image by Adina Voicu from Pixabay

Menyimpan Hasil Komputasi ke dalam Type Variable

Contoh

// NOPE ❌
type Something<T> =
  | Array<Some<Fancy<T>, Or<Long<T>>>>
  | Promise<Some<Fancy<T>, Or<Long<T>>>>
  | { wrapped: Some<Fancy<T>, Or<Long<T>>> }
// YES ✅
type Something<T> = Some<Fancy<T>, Or<Long<T>>> extends infer U
  ? Array<U> | Promise<U> | { wrapped: U }
  : never

Penjelasan

Kadangkala kita harus melakukan manipulasi type yang cukup kompleks dan panjang. Tapi gak asyik kalo type yang compleks tadi harus muncul di beberapa tempat: kita musti copy-paste. Gak DRY banget bro. Bakal jadi asyik kalo Typescript menyediakan cara untuk menyimpan hasil sebuah manupulasi ke dalam sebuah variable lalu tinggal kita panggil variable tersebut ketika dibutuhkan.

Dan kita bisa melakukannya dengan formula ... extends infer TypeVar ? ... : never. Keyword infer behaves seperti pattern matching sebuah type. Dalam hal ini, type yang di-pattern match adalah keseluruhan type, jadi yang masuk ke dalam type variable U ya type-nya itu sendiri.

Looping Union Type

Contoh

// NOPE ❌
type Arrayify_Bad<T> = Array<T>
// YES ✅
type Arrayify_Good<T> = T extends unknown ? Array<T> : never

Penjelasan

Anggap aja kita punya sebuah union string | number. Dan kita ingin sebuah type yang bisa mengubahnya menjadi string[] | number[]. Jika kita gunakan Arrayify_Bad<string | number>, hasilnya bakalan jadi Array<string | number>, not something that we want. Arrayify_Good<string | number> justru menghasilkan string[] | number[]. Kenapa?

Karena generic type menjadi distributif ketika di-apply dengan conditional type. Bisa dibaca di dokumentasinya untuk Penjelasan lebih lanjut.

Sebaliknya, untuk mencegah union type terdistribusi, kita harus gunakan kurung kotak (tuple) di kedua sisi:

type Arrayify_NonDist<T> = [T] extends [unknown] ? Array<T> : never

type Result = Arrayify_NonDist<string | number>
// => Array<string | number>

Hilangkan Type Alias dengan Mapped Type

Contoh

type Human = { name: string, age: number, title: string }
type Cat   = { name: string, age: number, meow: boolean }

type Result = XOR<Human | Cat>
// => Maunya { title: string } | { meow: boolean }

/**
 * Get all keys in a union type
 */
type Keyof<T> = T extends unknown ? keyof T : never

/**
 * Get overlapping properties in a union type
 */
type XAND<T> = { [K in Extract<Keyof<T>, keyof T>]: T[K] }

/**
 * Get non-overlapping properties in a union type (the dual of XAND)
 */
type XOR<T> = keyof XAND<T> extends infer K
  ? T extends unknown
    ? Omit<T, K & string>
    : never
  : never

Penjelasan

Kamu gak perlu pusing-pusing memahami XAND atau XOR di atas, karena bukan itu poin yang ingin saya tunjukkan. Poinnya ada di reporting ketika hover variable Result:

Cryptic reporting

Menurut saya pribadi, keyword Omit dan banyaknya symbol | yang muncul sangat mengganggu dan bikin dahi mengernyit. Saya ingin hasil yang lebih straightforward. Bayangkan jika bukan hanya Omit yang ada di situ, tapi ada custom type lain (i.e dari 3rd party library) yang musti kita telusuri satu-satu. Bikin pusing.

Nah kita bisa expand type alias tadi untuk hasil inspeksi yang lebih readable dengan mapped type.

type Result = XOR<Human | Cat> extends infer T
  ? { [K in keyof T]: T[K] } // ⬅️ Simple mapped type
  : never

Ketika di-hover, hasil yang kita dapat:

Better reporting

Much better!

Kesimpulan

Saya harap tips-tips kecil di atas dapat membantu teman-teman untuk lebih enjoy memanipulasi type dengan Typescript. Stay well!

Edit on