Penulis artikel yang kami terjemahkan hari ini mengatakan TypeScript is awesome. Ketika pertama kali mulai menggunakan TS, ia sangat menyukai kebebasan yang melekat dalam bahasa ini. Semakin banyak upaya yang dilakukan seorang programmer dalam pekerjaannya dengan mekanisme TS-spesifik, semakin besar manfaat yang akan diterimanya. Kemudian ia menggunakan tipe anotasi hanya secara berkala. Kadang-kadang ia menggunakan peluang untuk penyelesaian kode dan petunjuk kompiler, tetapi terutama hanya mengandalkan visinya sendiri tentang tugas-tugas yang diselesaikannya.
Seiring waktu, penulis materi ini menyadari bahwa setiap kali ia mem-bypass kesalahan yang terdeteksi pada tahap kompilasi, ia meletakkan bom waktu dalam kodenya yang dapat meledak selama eksekusi program. Setiap kali dia "berjuang" dengan kesalahan menggunakan konstruksi sederhana
as any
, dia harus membayarnya dengan berjam-jam hard debugging.

Akibatnya, ia sampai pada kesimpulan bahwa lebih baik tidak melakukannya. Dia berteman dengan kompiler, mulai memperhatikan petunjuknya. Kompiler menemukan masalah dalam kode dan melaporkannya jauh sebelum mereka dapat menyebabkan kerusakan nyata. Penulis artikel, yang memandang dirinya sebagai pengembang, menyadari bahwa kompiler adalah sahabatnya, karena melindungi dirinya dari dirinya sendiri. Bagaimana seseorang tidak dapat mengingat kata-kata Albus Dumbledore: "Dibutuhkan banyak keberanian untuk berbicara melawan musuhmu, tetapi tidak kurang dari itu diperlukan untuk berbicara melawan temanmu."
Tidak peduli seberapa bagus kompilernya, tidak selalu mudah untuk menyenangkannya. Terkadang menghindari penggunaan jenis
any
sangat sulit. Dan kadang-kadang tampaknya
any
solusi satu-satunya yang masuk akal untuk beberapa masalah.
Materi ini berfokus pada dua situasi. Dengan menghindari penggunaan jenis
any
di dalamnya, Anda dapat memastikan keamanan jenis kode, membuka kemungkinan untuk digunakan kembali, dan membuatnya intuitif.
Generik
Misalkan kita sedang mengerjakan basis data sebuah sekolah. Kami menulis fungsi pembantu yang sangat nyaman,
getBy
. Untuk mendapatkan objek yang mewakili siswa dengan namanya, kita dapat menggunakan perintah dari form
getBy(model, "name", "Harry")
. Mari kita lihat implementasi mekanisme ini (di sini, agar tidak menyulitkan kode, database diwakili oleh array biasa).
type Student = { name: string; age: number; hasScar: boolean; }; const students: Student[] = [ { name: "Harry", age: 17, hasScar: true }, { name: "Ron", age: 17, hasScar: false }, { name: "Hermione", age: 16, hasScar: false } ]; function getBy(model, prop, value) { return model.filter(item => item[prop] === value)[0] }
Seperti yang Anda lihat, kami memiliki fungsi yang baik, tetapi tidak menggunakan anotasi jenis, dan ketidakhadirannya juga berarti bahwa fungsi semacam itu tidak dapat disebut tipe-aman. Perbaiki
function getBy(model: Student[], prop: string, value): Student | null { return model.filter(item => item[prop] === value)[0] || null } const result = getBy(students, "name", "Hermione") // result: Student
Jadi fungsi kita sudah terlihat jauh lebih baik. Kompiler sekarang tahu jenis hasil yang diharapkan darinya, ini akan berguna nanti. Namun, untuk mencapai pekerjaan yang aman dengan tipe, kami mengorbankan kemungkinan menggunakan kembali fungsi. Bagaimana jika kita perlu menggunakannya untuk mendapatkan entitas lain? Tidak mungkin fungsi ini tidak dapat ditingkatkan dengan cara apa pun. Dan memang benar.
Dalam TypeScript, seperti dalam bahasa yang sangat diketik lainnya, kita dapat menggunakan generik, yang juga disebut "tipe generik", "tipe universal", "generalisasi".
Sebuah generik mirip dengan variabel biasa, tetapi alih-alih beberapa nilai, itu berisi definisi tipe. Kami menulis ulang kode fungsi kami sehingga alih-alih tipe
Student
itu akan menggunakan tipe universal
T
function getBy<T>(model: T[], prop: string, value): T | null { return model.filter(item => item[prop] === value)[0] } const result = getBy<Student>(students, "name", "Hermione") // result: Student
Cantik! Sekarang fungsi ini ideal untuk digunakan kembali, sementara keamanan jenis masih di pihak kita. Perhatikan bagaimana tipe
Student
secara eksplisit diatur di baris terakhir dari potongan kode di atas di mana
T
generik
T
. Hal ini dilakukan untuk membuat contoh sejelas mungkin, tetapi kompiler, pada kenyataannya, secara mandiri dapat menurunkan tipe yang diperlukan, jadi dalam contoh berikut ini kami tidak akan melakukan penyempurnaan tipe tersebut.
Jadi sekarang kami memiliki fungsi pembantu yang andal, cocok untuk digunakan kembali. Namun, itu masih bisa diperbaiki. Bagaimana jika kesalahan dibuat ketika memasukkan parameter kedua dan bukannya
"name"
tampaknya ada
"naem"
? Fungsi tersebut akan berperilaku seolah-olah siswa yang Anda cari sama sekali tidak ada dalam database, dan, yang paling tidak menyenangkan, itu tidak akan menghasilkan kesalahan. Ini dapat menghasilkan debugging jangka panjang.
Untuk melindungi dari kesalahan semacam itu, kami memperkenalkan tipe universal lain,
P
Dalam hal ini, perlu bahwa
P
menjadi kunci dari tipe
T
, oleh karena itu, jika
Student
digunakan di sini, maka perlu bahwa
P
menjadi string
"name"
,
"age"
atau
"hasScar"
. Begini cara melakukannya.
function getBy<T, P extends keyof T>(model: T[], prop: P, value): T | null { return model.filter(item => item[prop] === value)[0] || null } const result = getBy(students, "naem", "Hermione") // Error: Argument of type '"naem"' is not assignable to parameter of type '"name" | "age" | "hasScar"'.
Menggunakan obat generik dan
keyof
adalah trik yang sangat kuat. Jika Anda menulis program dalam IDE yang mendukung TypeScript, maka dengan memasukkan argumen, Anda dapat memanfaatkan kapabilitas pelengkapan otomatis, yang sangat nyaman.
Namun, kami belum selesai mengerjakan fungsi
getBy
. Dia memiliki argumen ketiga, jenis yang belum kita tentukan. Ini sama sekali tidak cocok untuk kita. Sampai sekarang, kita tidak bisa tahu sebelumnya apa yang seharusnya, karena itu tergantung pada apa yang kita lewati sebagai argumen kedua. Tetapi sekarang, karena kita memiliki tipe
P
, kita dapat secara dinamis menyimpulkan tipe untuk argumen ketiga. Jenis argumen ketiga pada akhirnya adalah
T[P]
. Akibatnya, jika
T
adalah
Student
, dan
P
adalah
"age"
, maka
T[P]
akan menjadi
number
tipe.
function getBy<T, P extends keyof T>(model: T[], prop: P, value: T[P]): T | null { return model.filter(item => item[prop] === value)[0] || null } const result = getBy(students, "age", "17") // Error: Argument of type '"17"' is not assignable to parameter of type 'number'. const anotherResult = getBy(students, "hasScar", "true") // Error: Argument of type '"true"' is not assignable to parameter of type 'boolean'. const yetAnotherResult = getBy(students, "name", "Harry") //
Saya harap sekarang Anda memiliki pemahaman yang sangat jelas tentang cara menggunakan obat generik dalam TypeScript, tetapi jika Anda ingin bereksperimen dengan sangat baik dengan semua yang Anda ingin bereksperimen dengan kode yang dibahas di sini, Anda dapat melihatnya di
sini .
Memperluas jenis yang ada
Kadang-kadang kita mungkin mengalami kebutuhan untuk menambahkan data atau fungsionalitas ke antarmuka yang kodenya tidak dapat kita ubah. Anda mungkin perlu mengubah objek standar, katakan - tambahkan beberapa properti ke objek
window
, atau memperpanjang perilaku beberapa perpustakaan eksternal seperti
Express
. Dan dalam kedua kasus, Anda tidak memiliki kemampuan untuk secara langsung mempengaruhi objek yang ingin Anda gunakan.
Kami akan melihat solusi untuk masalah ini dengan menambahkan fungsi
getBy
yang sudah Anda ketahui ke prototipe
Array
. Ini akan memungkinkan kami, menggunakan fungsi ini, untuk membangun konstruksi sintaksis yang lebih akurat. Saat ini, kita tidak berbicara tentang apakah baik atau buruk untuk memperluas objek standar, karena tujuan utama kami adalah mempelajari pendekatan yang sedang dipertimbangkan.
Jika kita mencoba menambahkan fungsi ke prototipe
Array
, kompiler tidak akan terlalu menyukai ini:
Array.prototype.getBy = function <T, P extends keyof T>( this: T[], prop: P, value: T[P] ): T | null { return this.filter(item => item[prop] === value)[0] || null; }; // Error: Property 'getBy' does not exist on type 'any[]'. const bestie = students.getBy("name", "Ron"); // Error: Property 'getBy' does not exist on type 'Student[]'. const potionsTeacher = (teachers as any).getBy("subject", "Potions") // ... ?
Jika kami mencoba meyakinkan kompilator dengan menggunakan konstruk
as any
secara berkala, kami akan membatalkan semua yang telah kami capai. Kompiler akan diam, tetapi Anda bisa melupakan pekerjaan aman dengan tipe.
Akan lebih baik untuk memperluas jenis
Array
, tetapi sebelum melakukan ini, mari kita bicara tentang bagaimana TypeScript menangani situasi ketika dua antarmuka dari jenis yang sama hadir dalam kode. Di sini skema tindakan sederhana diterapkan. Iklan akan, jika mungkin, digabungkan. Jika Anda tidak dapat menggabungkannya, sistem akan memberikan kesalahan.
Jadi kode ini berfungsi:
interface Wand { length: number } interface Wand { core: string } const myWand: Wand = { length: 11, core: "phoenix feather" }
Dan yang ini bukan:
interface Wand { length: number } interface Wand { length: string }
Sekarang, setelah berurusan dengan ini, kita melihat bahwa kita dihadapkan dengan tugas yang agak sederhana. Yaitu, yang perlu kita lakukan adalah mendeklarasikan antarmuka
Array<T>
dan menambahkan fungsi
getBy
ke dalamnya.
interface Array<T> { getBy<P extends keyof T>(prop: P, value: T[P]): T | null; } Array.prototype.getBy = function <T, P extends keyof T>( this: T[], prop: P, value: T[P] ): T | null { return this.filter(item => item[prop] === value)[0] || null; }; const bestie = students.getBy("name", "Ron"); // ! const potionsTeacher = (teachers as any).getBy("subject", "Potions") //
Harap dicatat bahwa sebagian besar kode yang cenderung Anda tulis dalam file modul, oleh karena itu, untuk membuat perubahan pada antarmuka
Array
, Anda akan memerlukan akses ke lingkup global. Anda dapat melakukan ini dengan menempatkan definisi tipe di dalam
declare global
. Misalnya, seperti ini:
declare global { interface Array<T> { getBy<P extends keyof T>(prop: P, value: T[P]): T | null; } }
Jika Anda akan memperluas antarmuka perpustakaan eksternal, maka kemungkinan besar Anda akan memerlukan akses ke
namespace
perpustakaan ini. Berikut adalah contoh cara menambahkan bidang
userId
ke
Request
dari perpustakaan
Express
:
declare global { namespace Express { interface Request { userId: string; } } }
Anda dapat bereksperimen dengan kode di bagian ini di
sini .
Ringkasan
Pada artikel ini, kami melihat teknik untuk menggunakan generik dan mengetik ekstensi dalam TypeScript. Kami berharap apa yang Anda pelajari hari ini akan membantu Anda menulis kode yang andal, mudah dimengerti, dan aman.
Pembaca yang budiman! Bagaimana perasaan Anda tentang semua jenis dalam TypeScript?
