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:
- Jika strukturnya sudah kompleks, terlalu banyak tanda
!
- 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:
- Tipe Generic yang menerima 2 parameter, disimbolkan
T
danU
. - Kita cek apakah tipe param
U
adalah key dari tipe paramT
atauU
propertinyaT
. - Jika ya, maka akses properti
U
dan kembalikan nilainya. - 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:
- Tipe Generic dengan 1 parameter, disimbolkan
T
. - Kita cek apakah tipe param
T
bertipe larik (Array). - Jika ya, maka kita ambil ril tipe yang disimbolkan
U
dari Array - 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.