Masukkan Password
55 Type Casting
Lecture ini membahas Type Casting di TypeScript. Konsepnya sangat berkaitan dengan materi sebelumnya tentang Type Guard, tetapi tujuannya sedikit berbeda.
Kalau Type Guard lebih ke:
“Saya mengecek dulu tipe value ini apa.”
Sedangkan Type Casting lebih ke:
“Saya sebagai developer tahu tipe value ini, jadi saya ingin memberi tahu TypeScript untuk memperlakukannya sebagai tipe tersebut.”
Kita bahas pelan-pelan dari masalah yang ada di lecture.
1. Apa itu Type Casting?
Type Casting adalah proses memberi tahu TypeScript bahwa suatu value harus diperlakukan sebagai tipe tertentu.
Misalnya TypeScript menganggap:
value: Element
Tetapi kita sebagai developer tahu:
“Value ini sebenarnya adalah
HTMLInputElement.”
Maka kita bisa mengatakan kepada TypeScript:
value as HTMLInputElement
Dengan begitu TypeScript akan memperlakukan value tersebut sebagai:
HTMLInputElement
2. Kenapa kita membutuhkan Type Casting?
Contoh kasus dari lecture adalah HTML.
Misalnya kita punya:
<input id="firstName" type="text">
Kita ingin mengambil element tersebut menggunakan TypeScript.
Kita bisa menggunakan:
const firstName = document.querySelector("#firstName");
Masalahnya, TypeScript tidak selalu tahu bahwa #firstName adalah <input>.
TypeScript bisa menganggap hasilnya:
Element | null
Padahal kita sebagai developer tahu:
“Saya sendiri yang membuat HTML-nya.
#firstNameadalah<input>.”
Jadi kita ingin memberi tahu TypeScript:
Element | null
↓
HTMLInputElement
Itulah salah satu kegunaan Type Casting.
3. Kenapa querySelector() bisa menghasilkan null?
Perhatikan:
const firstName = document.querySelector("#firstName");
querySelector() mencari element berdasarkan selector.
Tetapi bagaimana kalau element tersebut tidak ditemukan?
Misalnya HTML-nya tidak punya:
<input id="firstName">
Maka hasilnya:
null
Karena itu TypeScript memberikan tipe:
Element | null
Artinya:
firstName bisa:
Element
ATAU
null
Ini sebenarnya sangat masuk akal.
4. Masalah pertama: null
Misalnya kita langsung menulis:
const firstName = document.querySelector("#firstName");
firstName.value;
TypeScript akan protes.
Karena:
firstName: Element | null
Ada kemungkinan:
firstName === null
Kalau firstName adalah null, kita tidak bisa melakukan:
firstName.value
Karena null tidak punya property value.
5. Operator ! atau Non-null Assertion
Kalau kita benar-benar yakin element tersebut selalu ada, kita bisa menggunakan:
const firstName = document.querySelector("#firstName")!;
Tanda:
!
di sini disebut non-null assertion operator.
Artinya kita memberi tahu TypeScript:
“Tenang, saya yakin value ini tidak
nullatauundefined.”
Sehingga:
const firstName = document.querySelector("#firstName")!;
TypeScript tidak lagi menganggap:
Element | null
tetapi:
Element
6. Tapi masih ada masalah kedua
Sekarang kita sudah menghilangkan kemungkinan null.
Namun:
firstName
masih dianggap:
Element
Masalahnya, Element secara umum belum tentu memiliki property:
value
Contohnya ada banyak jenis HTML element:
<div></div>
<p></p>
<span></span>
<button></button>
<input>
Semuanya merupakan HTML element, tetapi tidak semuanya memiliki:
.value
Property value memang dimiliki oleh element tertentu seperti:
HTMLInputElement
HTMLTextAreaElement
HTMLSelectElement
Jadi TypeScript berkata:
“Kamu bilang ini
Element. Saya tidak bisa menjaminElementmemiliki.value.”
7. Kita tahu bahwa element tersebut adalah <input>
Karena HTML kita:
<input id="firstName" type="text">
kita sebagai developer tahu:
#firstName
↓
<input>
↓
HTMLInputElement
Tetapi TypeScript belum mendapatkan informasi tersebut dari selector secara cukup spesifik dalam contoh lecture.
Maka kita bisa melakukan Type Casting.
8. Cara pertama: Angle Brackets
Cara pertama menggunakan:
<Type>
Misalnya:
const firstName =
<HTMLInputElement>document.querySelector("#firstName")!;
Artinya:
“Perlakukan hasil
querySelector()ini sebagaiHTMLInputElement.”
Sekarang TypeScript tahu:
firstName: HTMLInputElement
Dan kita bisa:
firstName.value;
9. Kenapa sekarang .value bisa digunakan?
Sebelumnya:
firstName
↓
Element
TypeScript hanya tahu:
“Ini sebuah HTML Element.”
Setelah casting:
firstName
↓
HTMLInputElement
TypeScript tahu:
“Ini HTML Input Element.”
HTMLInputElement mempunyai property:
value
Jadi:
firstName.value
sekarang valid.
10. Contoh lengkap
Misalnya HTML:
<input id="firstName" type="text">
TypeScript:
const firstName =
<HTMLInputElement>document.querySelector("#firstName")!;
firstName.value = "John";
Maka input di browser akan berisi:
John
11. Cara kedua: as
Cara yang lebih umum untuk Type Casting di TypeScript adalah menggunakan:
as
Contohnya:
const firstName =
document.querySelector("#firstName") as HTMLInputElement;
Sekarang TypeScript menganggap:
firstName: HTMLInputElement
dan kita bisa:
firstName.value = "John";
12. Perbandingan dua syntax
Ada dua syntax utama yang dibahas di lecture.
Cara 1 — Angle Brackets
const input =
<HTMLInputElement>document.querySelector("#firstName");
Cara 2 — as
const input =
document.querySelector("#firstName") as HTMLInputElement;
Keduanya mempunyai tujuan yang sama:
hasil querySelector
↓
HTMLInputElement
13. Kenapa as lebih sering digunakan?
Karena syntax angle brackets bisa bermasalah ketika kita menggunakan JSX, misalnya pada React.
Perhatikan:
const input = <HTMLInputElement>element;
Di TypeScript biasa, ini bisa berarti:
“Cast
elementmenjadiHTMLInputElement.”
Tetapi dalam JSX, syntax:
<HTMLInputElement>
bisa dianggap sebagai JSX element/tag.
Jadi terjadi konflik antara:
TypeScript Type Assertion
vs
JSX Syntax
Karena itu ketika menggunakan React/JSX, biasanya kita menggunakan:
const input = element as HTMLInputElement;
Jadi untuk praktik modern:
Biasakan menggunakan
asuntuk type casting/type assertion.
14. Sebenarnya istilah yang lebih tepat: Type Assertion
Ada sedikit terminology yang penting.
Di TypeScript, syntax:
value as SomeType
secara resmi sering disebut Type Assertion.
Begitu juga:
<SomeType>value
adalah bentuk Type Assertion.
Orang sering menyebutnya Type Casting, dan lecture juga menggunakan istilah tersebut.
Untuk pemahaman praktis, kamu bisa menganggap:
Type Casting / Type Assertion
↓
memberi tahu compiler
"perlakukan value ini sebagai tipe X"
Tetapi perlu diingat:
Ini bukan berarti JavaScript benar-benar mengubah object tersebut menjadi tipe baru.
15. Ini sangat penting: Type Casting tidak mengubah value
Misalnya:
const value = "123";
const numberValue = value as number;
TypeScript memang bisa memiliki aturan tertentu terkait assertion, dan contoh seperti ini tidak selalu diizinkan secara langsung karena tipe tersebut tidak cukup kompatibel.
Tetapi yang penting adalah memahami konsepnya:
as SomeType
tidak melakukan konversi runtime.
Misalnya:
value as HTMLInputElement
tidak mengubah:
Element
menjadi:
HTMLInputElement
di JavaScript.
Tidak ada object baru yang dibuat.
Tidak ada conversion khusus yang terjadi.
Ini terutama merupakan informasi untuk TypeScript compiler.
16. Analogi Type Casting
Bayangkan ada sebuah kotak.
Label dari sistem:
┌─────────────────┐
│ OBJECT │
└─────────────────┘
Kamu tahu isi kotak sebenarnya adalah:
┌─────────────────┐
│ HTML INPUT │
│ │
│ value │
│ type │
│ ... │
└─────────────────┘
Kamu berkata kepada TypeScript:
“Saya tahu lebih banyak daripada informasi yang kamu punya. Anggap object ini sebagai
HTMLInputElement.”
Dengan:
object as HTMLInputElement
TypeScript kemudian memberikan autocomplete dan type checking berdasarkan HTMLInputElement.
17. Tapi TypeScript mempercayai kita
Ini bagian yang sangat penting.
Type Assertion seperti:
element as HTMLInputElement
pada dasarnya mengatakan:
“Compiler, percaya saya.”
TypeScript tidak selalu melakukan pengecekan runtime untuk membuktikan assertion tersebut benar.
Misalnya:
<div id="firstName"></div>
Kemudian kita menulis:
const input =
document.querySelector("#firstName") as HTMLInputElement;
Kita sedang mengatakan:
“
#firstNameadalah input.”
Padahal sebenarnya:
#firstName = div
TypeScript bisa menerima assertion tersebut dalam situasi yang kompatibel, tetapi saat runtime object tersebut tetaplah <div>.
Akibatnya, kita bisa mendapatkan behavior/error yang tidak kita harapkan ketika menggunakan API khusus input.
18. Jadi jangan asal melakukan as
Ini prinsip yang sangat penting:
Type Assertion bukan cara untuk memperbaiki tipe yang salah.
Type Assertion adalah cara untuk memberitahu TypeScript mengenai sesuatu yang kita sudah tahu benar tetapi tidak dapat disimpulkan compiler.
Contoh yang masuk akal:
const input =
document.querySelector("#firstName") as HTMLInputElement;
Jika kita benar-benar tahu HTML tersebut selalu memiliki:
<input id="firstName">
maka assertion masuk akal.
19. Bagaimana kalau querySelector() bisa menghasilkan null?
Nah, ini bagian lain yang penting dari lecture.
Misalnya:
const input =
document.querySelector("#firstName") as HTMLInputElement;
Kita menganggap:
input = HTMLInputElement
Tetapi kalau element tidak ditemukan?
Runtime sebenarnya:
input = null
Maka kita memiliki potensi masalah.
Jangan berpikir:
as HTMLInputElement
secara otomatis membuat element menjadi ada.
Tidak.
as tidak membuat element muncul.
20. Kalau mungkin null, lakukan pengecekan
Cara yang lebih aman adalah:
const firstName = document.querySelector("#firstName");
if (firstName) {
const input = firstName as HTMLInputElement;
input.value = "John";
}
Alurnya:
querySelector()
↓
Element | null
↓
if (firstName)
↓
dipastikan bukan null
↓
as HTMLInputElement
↓
HTMLInputElement
↓
.value
Ini jauh lebih aman.
21. Bahkan bisa langsung seperti ini
Kita juga bisa menulis:
const firstName =
document.querySelector("#firstName");
if (firstName) {
(firstName as HTMLInputElement).value = "John";
}
Perhatikan tanda kurung:
(firstName as HTMLInputElement).value
Kenapa?
Karena kita ingin mengatakan:
“Cast
firstNamedulu menjadiHTMLInputElement, baru ambil.value.”
22. Kenapa tidak menggunakan ! saja?
Kita mungkin melihat:
const input =
document.querySelector("#firstName")!;
Ini hanya menghilangkan kemungkinan:
null
Tetapi tidak mengubah:
Element
menjadi:
HTMLInputElement
Jadi dua masalah tersebut berbeda.
!
Menjawab:
“Saya yakin ini bukan
null.”
as HTMLInputElement
Menjawab:
“Saya yakin ini adalah
HTMLInputElement.”
Jadi:
document.querySelector("#firstName")!
dan:
document.querySelector("#firstName") as HTMLInputElement
memiliki fungsi yang berbeda.
23. Bisa juga menggunakan keduanya
Misalnya:
const input =
document.querySelector("#firstName")! as HTMLInputElement;
Artinya:
!→ saya yakin tidaknullas HTMLInputElement→ saya yakin ini input element
Tetapi secara readability, sering lebih enak menggunakan pengecekan if jika memang ada kemungkinan element tidak ditemukan.
24. Type Guard vs Type Casting
Ini sangat penting karena dua materi ini berdekatan.
Type Guard
Kita mengecek:
if (typeof value === "string") {
...
}
TypeScript mendapatkan bukti dari kondisi tersebut dan melakukan narrowing.
Type Casting / Assertion
Kita memberi tahu TypeScript:
const input = element as HTMLInputElement;
Kita berkata:
“Percaya saya, ini
HTMLInputElement.”
Jadi:
Type Guard
↓
"ayo kita cek dulu"
Type Assertion
↓
"saya sudah tahu, percayalah"
25. Analogi sederhana
Misalnya ada seseorang datang.
Kita belum tahu siapa dia.
Type Guard
Kita bertanya:
“Apakah kamu John?”
Dia menjawab:
“Ya.”
Kita kemudian mempersempit informasi berdasarkan pengecekan.
Type Assertion
Kamu berkata:
“Saya kenal orang ini. Ini John.”
Tanpa melakukan pengecekan tambahan.
Kalau ternyata dia bukan John?
Program bisa bermasalah.
Jadi Type Assertion membutuhkan keyakinan developer.
26. Contoh nyata lain
Misalnya kita punya:
const element = document.querySelector("#username");
TypeScript:
Element | null
Kita tahu HTML-nya:
<input id="username" type="text">
Maka:
const username =
document.querySelector("#username") as HTMLInputElement;
Sekarang:
username.value
bisa digunakan.
27. Jangan salah mengartikan as
Kalau kita menulis:
const username =
document.querySelector("#username") as HTMLInputElement;
bukan berarti:
Element
↓
DI-CONVERT
↓
HTMLInputElement
Lebih tepat:
Element
↓
TypeScript diberi tahu:
"anggap ini HTMLInputElement"
↓
Compiler memberikan akses ke API HTMLInputElement
Object aslinya tetap object yang sama.
28. Ringkasan ! vs as
Ini bagian yang sangat bagus untuk dihafalkan:
| Syntax | Tujuan |
|---|---|
! | Mengatakan value bukan null/undefined |
as Type | Mengatakan value harus diperlakukan sebagai Type |
if (value) | Mengecek value secara nyata sebelum digunakan |
typeof | Mengecek tipe primitive |
instanceof | Mengecek instance class |
in | Mengecek keberadaan property |
Contoh:
const element = document.querySelector("#name");
Awalnya:
Element | null
Dengan:
element!
menjadi:
Element
Dengan:
element as HTMLInputElement
menjadi:
HTMLInputElement
29. Pola aman yang perlu kamu ingat
Kalau element pasti ada dan kamu benar-benar yakin:
const input =
document.querySelector("#firstName") as HTMLInputElement;
Kalau element mungkin tidak ada, lebih aman:
const element = document.querySelector("#firstName");
if (element) {
const input = element as HTMLInputElement;
input.value = "John";
}
Atau kita bisa menggabungkan pengecekan dengan casting:
const element = document.querySelector("#firstName");
if (element) {
(element as HTMLInputElement).value = "John";
}
30. Inti besar lecture
Lecture ini sebenarnya menjawab satu masalah:
“Bagaimana kalau TypeScript memberikan tipe yang terlalu umum, sementara saya sebagai developer tahu tipe sebenarnya?”
Contohnya:
TypeScript tahu:
Element | null
Developer tahu:
HTMLInputElement dan pasti ada
Maka kita bisa menggunakan:
!
untuk masalah null:
element!
dan:
as HTMLInputElement
untuk masalah tipe:
element as HTMLInputElement
31. Gambaran visual konsepnya
document.querySelector("#firstName")
│
↓
Element | null
│
┌─────┴─────┐
│ │
null Element
│ │
│ "Saya tahu ini
│ sebenarnya input"
│ │
│ ↓
│ as HTMLInputElement
│ │
│ ↓
│ HTMLInputElement
│ │
│ ↓
│ .value
│
perlu di-handle
32. Kesimpulan yang harus kamu kuasai
Ada dua cara Type Assertion/Type Casting yang dibahas:
1. Angle Brackets
const input =
<HTMLInputElement>element;
2. as
const input =
element as HTMLInputElement;
Untuk React/JSX, biasanya gunakan:
const input =
element as HTMLInputElement;
karena syntax angle brackets bisa bentrok dengan JSX.
Dan satu hal yang paling penting:
Type Casting tidak mengubah object saat runtime. Type Casting hanya memberi tahu TypeScript bagaimana kita ingin memperlakukan value tersebut.
Sementara:
const input = document.querySelector("#firstName")!;
berarti:
“Saya yakin hasilnya bukan
null.”
Sedangkan:
const input =
document.querySelector("#firstName") as HTMLInputElement;
berarti:
“Saya yakin hasilnya adalah
HTMLInputElement.”
Dan kalau kita tidak yakin element-nya ada, jangan asal menggunakan !. Lebih aman melakukan pengecekan:
const element = document.querySelector("#firstName");
if (element) {
const input = element as HTMLInputElement;
input.value = "John";
}
Jadi gampangnya:
!menangani keyakinan bahwa value tidaknull, sedangkanasmenangani keyakinan kita tentang tipe value tersebut.