Mengecualikan tipe null dan undefined pada tipe data TypeScript

14 April 2021 - Abdul Fattah Ikhsan

Bagikan:

Bahasan kali ini tentang TypeScript. ada yang belum tahukah dengan TypeScript? atau sudah akrabkah? barangkali sering berantem dengan omelan TypeScript di kode ๐Ÿ˜†.

Tulisan ini lebih ke tip dan trik yang sering kita temui ketika menulis kode di TypeScript. Berikut contoh-contoh sederhana dan contoh lanjutan. Mantap ๐Ÿ‘!

Kita tahu TypeScript adalah Superset JavaScript. Definisi lengkapnya adalah syntax sugar dari JavaScript, bahasa yang dibuat mirip dengan JavaScript idiom (rasa JavaScript) yang ditambah dengan pemeriksaan tipe data yang ketat.

Masalah

Ketika menulis kode program, Kalian pasti pernah berada dalam situasi membuat peubah (variable) yang memiliki kemungkinan menerima nilai null atau undefined. Biasanya data ini datang dari server, kemudian Kalian mencoba untuk mengatasi masalah ini dengan:

Menggunakan tipe Union

type Post = { title: string; content: string | null | undefined; }
type Posts = Array<Post | null | undefined> | null | undefined

Kodenya lumayan panjang, Kemudian kalian mencoba untuk membuat tipe baru agar tidak banyak pengulangan:

Menggunakan tipe Generic

type Maybe<T> = T | null | undefined
type Post = { id: string; title: string; content: Maybe<string> }
type Posts = Maybe<Array<Maybe<Post>>>

Menggunakan tanda seru !

Ketika Kalian mencoba akses properti yang ada di Post. Maka dengan mudah kita bisa menggunakan negasi / tanda seru di akhir nama properti. Gunanya untuk memberi tahu kompiler TypeScript bahwa properti yang kita akses dipastikan tidak null atau undefined.

function getPostContent(post: Post): string {
   return post.content!
}

Kemudian bagaimana Kalian membuat perulangan pada Array Posts?

function getPostTitles(allPosts: Posts): Array<string> {
  return allPosts!.map(post => post!.title)
}

Okay, mudah bukan? Implementasi sederhana ini punya kekurangan, yaitu:

  1. Jika strukturnya sudah kompleks, terlalu banyak tanda !
  2. Jika strukturnya kompleks, bagaimana kita memecah tipe-tipe itu menjadi tipe-tipe yang lebih kecil?

Struktur kompleks, memecah tipe jadi beberapa tipe

Ketika Kalian punya struktur kompleks seperti tipe data hasil dari GraphQL codegen. Bagi yang belum tahu, Alat ini membantu membuatkan definisi tipe-tipe TypeScript ke project berdasarkan skema GraphQL server. Katakanlah Kita punya struktur tipe seperti ini:

type PostDetailQueryType = (
  { __typename?: 'RootQuery' }
  & { post: Maybe<(
    { __typename?: 'Post' }
    & Pick<Post, 'id' | 'title' | 'content'>
    & { author: Maybe<(
      { __typename?: 'NodeWithAuthorToUserConnectionEdge' }
      & { node: Maybe<(
        { __typename?: 'User' }
        & Pick<User, 'slug' | 'name'>
        & { avatar: Maybe<(
          { __typename?: 'Avatar' }
          & Pick<Avatar, 'url'>
        )> }
      )> }
    )> }
  )> }
);

Hoho ๐Ÿ˜…, hampir susah untuk dibaca ya. Padahal cuma potongan sedikit saja. Kurang lebih struktur datanya itu seperti ini:

{
   "PostDetailQueryType":{
      "post":{
         "__typename": "Post",
         "id":"1",
         "title":"typescript",
         "content":"content....",
         "author":{
            "__typename": "NodeWithAuthorToUserConnectionEdge",
            "node":{
               "__typename": "User",
               "slug":"abdul-fattah-ikhsan",
               "name":"Abdul Fattah Ikhsan",
               "avatar":{
                  "__typename": "Avatar",
                  "url":"http://avatar-url"
               }
            }
         }
      }
   }
}

Bagaimana caranya kita membuat tipe Author dari tipe PostDetailQueryType?

Keluarkan tipe data dari null dan undefined

Kalian harus mengeluarkan tipe null dan undefined dulu. Baru selanjutnya bisa membuat tipe Author-nya. Ini adalah inti dari judul tulisan ini ๐Ÿ˜‰.

Menggunakan tipe Utilitas NonNullable

type Author = NonNullable<PostDetailTypeQuery['post']>['author'];

Atau jika Kalian ingin tipe Author-nya tidak mempunyai kemungkinan null atau undefined. Maka:

type Author = NonNullable<NonNullable<PostDetailTypeQuery['post']>['author']>;

Implementasi diatas bisa Kalian perbarui dengan membuat tipe utilitas baru agar tidak terlalu banyak memakai sintaks [] (tuple).

type Unpack<T, U> = U extends keyof T ? T[U] : never;

Mari Kita analisa tipe utilitas Unpack diatas:

  1. Tipe Generic yang menerima 2 parameter, disimbolkan T dan U.
  2. Kita cek apakah tipe param U adalah key dari tipe param T atau U propertinya T.
  3. Jika ya, maka akses properti U dan kembalikan nilainya.
  4. Jika bukan, maka kembalikan never.
type Author = Unpack<NonNullable<PostDetailTypeQuery['post']>, 'author'>;

Lihatkan, Kita menggunakan Unpack untuk akses propertinya. Selanjutnya, Kalian bisa menambahkan NonNullable lagi didepannya jika diperlukan. Bisakan?

Bagaimana dengan Array yang punya kemungkinan null atau undefined?

Bagaimana dengan Array (larik) yang punya kemungkinan null atau undefined? Seperti ini:

type PostDetailWithCategoriesQuery = (
  { __typename?: 'RootQuery' }
  & { post: Maybe<(
    { __typename?: 'Post' }
    & Pick<Post, 'id' | 'title' | 'content'>
    & { categories: Maybe<(
      { __typename?: 'PostToCategoryConnection' }
      & { nodes: Maybe<Array<Maybe<(
        { __typename?: 'Category' }
        & Pick<Category, 'id' | 'slug' | 'name' | 'uri'>
      )>>> }
    )> }
  )> }
);

Kurang lebih struktur datanya seperti ini:

{
   "PostDetailWithCategoriesQuery":{
      "post":{
         "__typename":"Post",
         "id":"1",
         "title":"typescript",
         "content":"content....",
         "categories":{
            "__typename":"PostToCategoryConnection",
            "nodes":[
                {
                  "__typename":"Category",
                  "id":"1",
                  "slug":"javascript",
                  "name":"JavaScript",
                  "uri":"/topik/javascript/"
                },
            ]
         }
      }
   }
}

Lebih spesifik pertanyaannya, Bagaimana Kalian membuat tipe Category dari tipe PostDetailWithCategoriesQuery?

Keluarkan tipe data dari Array, null dan undefined

Kalian gunakan kembali utilitas yang ada sebelumnya, ditambah membuat utilitas baru. Katakanlah namanya UnpackArray.

type UnpackArray<T> = T extends (infer U)[] ? U : T;

Perhatikan disana ada kata kunci infer. Mari Kita analisa tipe UnPackArray diatas:

  1. Tipe Generic dengan 1 parameter, disimbolkan T.
  2. Kita cek apakah tipe param T bertipe larik (Array).
  3. Jika ya, maka kita ambil ril tipe yang disimbolkan U dari Array
  4. Jika tidak, maka kembalikan tipe param T. Artinya, biarkan apa adanya.

Sekarang implementasikan untuk membuat tipe category.

type CategoriesOnPost = NonNullable<Unpack<NonNullable<PostDetailWithCategoriesQuery['post']>, 'categories'>>;
type CategoryNodes = Unpack<NonNullable<CategoriesOnPost>, 'nodes'>;
type Category = UnpackArray<NonNullable<CategoryNodes>>;

Lumayan panjang ya, sampai membuat 3 tipe hanya untuk membuat tipe category. Selanjutnya, Kalian bisa menambahkan NonNullable lagi didepannya jika diperlukan. Bisakan?

Penutup

Bagaimana? pahamkan? Saya sudah kasih contoh-contoh dari yang mudah sampai yang susah ๐Ÿ˜Ž. Kalian bisa cek langsung di github gist Saya dan copas ke TypeScript Playground.

Pada solusi terakhir sebenarnya masih bisa Kita perbaiki dengan menulis utilitas baru dengan teknik rekursif. Namun, saat tulisan ini dibuat, Saya masih belum terlalu paham. Jadi, dilain kesempatan saja ya. Atau barangkali ada yang mau menambahi?


Saya cukupkan tulisan kali ini. Terima kasih sudah membaca tulisan ini sampai selesai, Tulisan ini saya dedikasikan untuk Indonesia lebih baik. Semoga bermanfaat dan yuk lanjut kodingnya!

Apabila ada pertanyaan atau ingin kolaborasi bisa kontak langsung di telegram.

#programming
#Tipe Data
#TypeScript
ยฉ 2021 - 2023 lanjutkoding.com