TypeScript Efektif: 62 Cara untuk Meningkatkan Kode Anda

gambarHalo para Penduduk! Buku Dan Vanderkam akan sangat berguna bagi mereka yang sudah berpengalaman dengan JavaScript dan TypeScript. Tujuan dari buku ini bukan untuk mendidik pembaca untuk menggunakan alat-alat tersebut, tetapi untuk membantu mereka meningkatkan profesionalisme mereka. Setelah membacanya, Anda akan mendapatkan pemahaman yang lebih baik tentang cara kerja komponen TypeScript, menghindari banyak jebakan dan jebakan, dan mengembangkan keterampilan Anda. Sementara panduan referensi akan menunjukkan kepada Anda lima cara berbeda dalam menggunakan bahasa untuk menyelesaikan tugas yang sama, sebuah buku yang "efektif" akan menjelaskan mana yang lebih baik dan mengapa.



Struktur buku



Buku adalah kumpulan esai pendek (aturan). Aturan dikelompokkan menjadi beberapa bagian tematik (bab), yang dapat diakses secara mandiri tergantung pada pertanyaan yang menarik.



Setiap tajuk aturan berisi tip, jadi periksa daftar isi. Jika, misalnya, Anda menulis dokumentasi dan ragu apakah akan menulis informasi tipe, lihat daftar isi dan aturan 30 (“Jangan ulangi informasi tipe dalam dokumentasi”).

Hampir semua kesimpulan dalam buku ini ditunjukkan dengan menggunakan contoh kode. Saya pikir Anda, seperti saya, cenderung membaca buku teknis dengan melihat contoh dan hanya melewati bagian teks. Tentu saja, saya harap Anda membaca penjelasannya dengan cermat, tetapi saya telah membahas poin-poin utama dalam contoh.



Setelah membaca setiap tip, Anda dapat memahami dengan tepat bagaimana dan mengapa itu akan membantu Anda menggunakan TypeScript dengan lebih efektif. Anda juga akan mengerti jika ternyata tidak dapat digunakan dengan cara tertentu. Saya ingat contoh yang diberikan oleh Scott Myers, penulis Effective C ++: pengembang perangkat lunak rudal mungkin telah mengabaikan saran untuk mencegah kebocoran sumber daya karena program mereka dihancurkan ketika rudal mencapai target. Saya tidak mengetahui keberadaan roket dengan sistem kontrol yang ditulis dalam JavaScript, tetapi perangkat lunak semacam itu tersedia di teleskop James Webb. Jadi berhati-hatilah.



Setiap aturan diakhiri dengan blok "Harus Diingat". Dengan melihatnya sekilas, Anda bisa mendapatkan gambaran umum tentang materi dan menyoroti hal utama. Tapi saya sangat merekomendasikan membaca seluruh aturan.



Kutipan. ATURAN 4. Biasakan mengetik struktural



JavaScript tidak sengaja diketik bebek: jika Anda meneruskan nilai dengan properti yang benar ke suatu fungsi, cara Anda mendapatkan nilai itu tidak peduli. Dia hanya menggunakannya. TypeScript memodelkan perilaku ini, yang terkadang mengarah ke hasil yang tidak diharapkan, karena pemahaman validator tentang tipe mungkin lebih luas daripada pemahaman Anda. Mengembangkan keterampilan mengetik terstruktur akan memungkinkan Anda untuk lebih merasakan di mana sebenarnya ada kesalahan dan menulis kode yang lebih dapat diandalkan.



Misalnya, Anda bekerja dengan pustaka fisika dan Anda memiliki tipe vektor 2D:



interface Vector2D {
   x: number;
   y: number;
}


Anda menulis fungsi untuk menghitung panjangnya:



function calculateLength(v: Vector2D) {
   return Math.sqrt(v.x * v.x + v.y * v.y);
}


dan masukkan definisi vektor bernama:



interface NamedVector {
   name: string;
   x: number;
   y: number;
}


Fungsi countLength akan bekerja dengan NamedVector karena berisi properti x dan y, yaitu bilangan. TypeScript memahami ini:



const v: NamedVector = { x: 3, y: 4, name: 'Zee' };
calculateLength(v); // ok,   5.


Hal yang menarik adalah Anda tidak mendeklarasikan hubungan antara Vector2D dan NamedVector. Anda juga tidak perlu menulis eksekusi countLength alternatif untuk NamedVector. Sistem jenis TypeScript mensimulasikan perilaku waktu proses JavaScript (aturan 1), yang memungkinkan NamedVector memanggil countLength berdasarkan strukturnya yang sebanding dengan Vector2D. Karenanya ungkapan "pengetikan struktural".



Tapi itu juga bisa menimbulkan masalah. Katakanlah Anda menambahkan jenis vektor 3D:



interface Vector3D {
   x: number;
   y: number;
   z: number;
}


dan tulis fungsi untuk menormalkan vektor (buat panjangnya sama dengan 1):



function normalize(v: Vector3D) {
   const length = calculateLength(v);
   return {
      x: v.x / length,
      y: v.y / length,
      z: v.z / length,
   };
}


Jika Anda memanggil fungsi ini, kemungkinan besar Anda akan mendapatkan lebih dari satu panjang:



> normalize({x: 3, y: 4, z: 5})
{ x: 0.6, y: 0.8, z: 1 }


Apa yang salah dan mengapa TypeScript tidak melaporkan bug?



Bugnya adalah countLength bekerja dengan vektor 2D, sedangkan normalisasi bekerja dengan 3D. Oleh karena itu, komponen z diabaikan selama normalisasi.



Mungkin tampak aneh bahwa pemeriksa tipe tidak menangkap ini. Mengapa diperbolehkan memanggil countLength pada vektor 3D, meskipun tipenya berfungsi dengan vektor 2D?

Apa yang berhasil dengan baik dengan nama telah menjadi bumerang di sini. Memanggil countLength pada objek {x, y, z} tidak akan menimbulkan kesalahan. Oleh karena itu, modul pemeriksaan tipe tidak mengeluh, yang pada akhirnya mengarah pada munculnya bug. Jika Anda ingin kesalahan terdeteksi dalam kasus seperti itu, lihat aturan 37.



Saat menulis fungsi, mudah untuk membayangkan bahwa mereka akan dipanggil oleh properti yang Anda deklarasikan, dan tidak ada yang lain. Ini disebut tipe "disegel" atau "tepat" dan tidak dapat diterapkan dalam sistem tipe TypeScript. Suka atau tidak, jenisnya terbuka di sini.



Terkadang hal ini menimbulkan kejutan:



function calculateLengthL1(v: Vector3D) {
   let length = 0;
   for (const axis of Object.keys(v)) {
      const coord = v[axis];
                       // ~~~~~~~     "any",  
                       //  "string"    
                       //    "Vector3D"
      length += Math.abs(coord);
   }
   return length;
}


Mengapa ini salah? Karena sumbu adalah salah satu kunci v dari Vector3D, sumbu harus x, y, atau z. Dan menurut deklarasi Vector3D asli, semuanya adalah angka. Oleh karena itu, bukankah jenis coord juga harus berupa angka?



Ini bukan kesalahan palsu. Kita tahu bahwa Vector3D didefinisikan secara ketat dan tidak memiliki properti lain. Meskipun dia bisa:



const vec3D = {x: 3, y: 4, z: 1, address: '123 Broadway'};
calculateLengthL1(vec3D); // ok,  NaN


Karena v mungkin bisa memiliki properti apa pun, sumbu bertipe string. Tidak ada alasan bagi TypeScript untuk menganggap v [axis] hanya sebagai angka. Saat melakukan iterasi pada objek, mungkin sulit untuk mendapatkan pengetikan yang benar. Kami akan kembali ke topik ini di Aturan 54, tetapi untuk saat ini kami tidak menggunakan loop:



function calculateLengthL1(v: Vector3D) {
   return Math.abs(v.x) + Math.abs(v.y) + Math.abs(v.z);
}


Pengetikan struktural juga dapat menyebabkan kejutan di kelas yang dibandingkan dengan penugasan properti yang memungkinkan:



class C {
   foo: string;
   constructor(foo: string) {
       this.foo = foo;
   }
}

const c = new C('instance of C');
const d: C = { foo: 'object literal' }; // ok!


Mengapa d bisa ditugaskan ke C? Ini memiliki properti foo yaitu string. Ini juga memiliki konstruktor (dari Object.prototype) yang dapat dipanggil dengan argumen (meskipun biasanya dipanggil tanpa argumen). Jadi strukturnya sama. Hal ini dapat menimbulkan kejutan jika Anda memiliki logika di konstruktor C dan menulis fungsi untuk memanggilnya. Ini adalah perbedaan yang signifikan dari bahasa seperti C ++ atau Java, di mana deklarasi parameter tipe C memastikan bahwa ia termasuk dalam C atau subkelasnya.



Pengetikan struktural sangat membantu saat menulis tes. Katakanlah Anda memiliki fungsi yang menjalankan kueri database dan memproses hasilnya.



interface Author {
   first: string;
   last: string;
}
function getAuthors(database: PostgresDB): Author[] {
   const authorRows = database.runQuery(`SELECT FIRST, LAST FROM
                                 AUTHORS`);
   return authorRows.map(row => ({first: row[0], last: row[1]}));
}


Untuk mengujinya, Anda dapat membuat tiruan PostgresDB. Namun, solusi yang lebih baik adalah dengan menggunakan pengetikan terstruktur dan menentukan antarmuka yang lebih sempit:



interface DB {
   runQuery: (sql: string) => any[];
}
function getAuthors(database: DB): Author[] {
   const authorRows = database.runQuery(`SELECT FIRST, LAST FROM
                                 AUTHORS`);
   return authorRows.map(row => ({first: row[0], last: row[1]}));
}


Anda masih bisa meneruskan postgresDB ke fungsi getAuthors di keluaran, karena ia memiliki metode runQuery. Pengetikan struktural tidak mewajibkan PostgresDB untuk melaporkan bahwa ia sedang menjalankan DB. TypeScript akan menemukannya untuk Anda.



Saat menulis tes, Anda juga bisa meneruskan objek yang lebih sederhana:



test('getAuthors', () => {
   const authors = getAuthors({
      runQuery(sql: string) {
         return [['Toni', 'Morrison'], ['Maya', 'Angelou']];
      }
   });
   expect(authors).toEqual([
      {first: 'Toni', last: 'Morrison'},
      {first: 'Maya', last: 'Angelou'}
   ]);
});


TypeScript akan menentukan bahwa tes DB sesuai dengan antarmuka. Pada saat yang sama, pengujian Anda sama sekali tidak memerlukan informasi apa pun tentang database keluaran: tidak diperlukan pustaka tiruan. Dengan memperkenalkan abstraksi (DB), kami membebaskan logika dari detail eksekusi (PostgresDB).



Keuntungan lain dari pengetikan struktural adalah dapat dengan jelas memutus ketergantungan antar pustaka. Untuk informasi lebih lanjut tentang topik ini, lihat aturan 51.



HARAP DIINGAT



JavaScript menggunakan pengetikan bebek, dan TypeScript memodelkannya menggunakan pengetikan terstruktur. Akibatnya, nilai yang ditetapkan ke antarmuka Anda mungkin memiliki properti yang tidak ditentukan dalam tipe yang dideklarasikan. Jenis di TypeScript tidak disegel.



Ingatlah bahwa kelas juga mematuhi aturan pengetikan struktural. Oleh karena itu, Anda mungkin mendapatkan sampel kelas yang berbeda dari yang diharapkan.



Gunakan pengetikan terstruktur untuk mempermudah pengujian item.


ATURAN 5. Batasi penggunaan tipe apapun



Sistem tipe di TypeScript bersifat bertahap dan selektif. Gradualitas diwujudkan dalam kemampuan untuk menambahkan tipe ke kode selangkah demi selangkah, dan selektivitas dalam kemampuan untuk menonaktifkan modul pemeriksaan tipe saat Anda membutuhkannya. Kunci untuk mengontrol dalam kasus ini adalah jenis apa pun:



   let age: number;
   age = '12';
// ~~~  '"12"'       'number'.
   age = '12' as any; // ok


Modul hak, menunjukkan kesalahan, tetapi dapat dihindari hanya dengan menambahkan sebagai apa saja. Saat Anda mulai bekerja dengan TypeScript, Anda tergoda untuk menggunakan jenis apa pun atau sebagai pernyataan apa pun jika Anda tidak memahami kesalahannya, tidak mempercayai validator, atau tidak ingin menghabiskan waktu untuk mengeja jenisnya. Namun perlu diingat bahwa setiap meniadakan banyak manfaat dari TypeScript, yaitu:



Mengurangi keamanan kode



Pada contoh di atas, menurut tipe yang dinyatakan, usia adalah angka. Tapi apapun diperbolehkan string untuk ditugaskan padanya. Validator akan menganggap bahwa ini adalah angka (seperti yang Anda nyatakan), yang akan menyebabkan kebingungan.



age += 1; // ok.    age "121".


Memungkinkan Anda untuk melanggar kondisi



Saat membuat fungsi, Anda menyetel kondisi bahwa, setelah menerima tipe data tertentu dari panggilan, itu akan menghasilkan tipe yang sesuai dalam output, yang dilanggar seperti ini:



function calculateAge(birthDate: Date): number {
   // ...
}

let birthDate: any = '1990-01-19';
calculateAge(birthDate); // ok


Parameter tanggal lahir harus Tanggal, bukan string. Jenis apa pun mengizinkan kondisi yang terkait dengan kalkulasi untuk dilanggar. Ini bisa sangat bermasalah karena JavaScript cenderung melakukan konversi tipe implisit. Karena itu, string akan gagal dalam beberapa kasus di mana nomor seharusnya, tetapi pasti akan gagal di tempat lain.



Menghilangkan dukungan untuk layanan bahasa



Ketika sebuah karakter diberi suatu tipe, layanan bahasa TypeScript dapat menyediakan substitusi otomatis yang sesuai dan dokumentasi kontekstual (Gambar 1.3).



gambar


Akan tetapi, dengan menetapkan tipe apapun ke karakter, Anda harus melakukan semuanya sendiri (Gambar 1.4).



gambar


Dan mengganti nama juga. Jika Anda memiliki tipe Person dan fungsi untuk memformat nama:



interface Person {
   first: string;
   last: string;
}

const formatName = (p: Person) => `${p.first} ${p.last}`;
const formatNameAny = (p: any) => `${p.first} ${p.last}`;


kemudian Anda dapat menyorot terlebih dahulu di editor dan memilih Ubah Nama Simbol untuk mengubah namanya menjadi nama pertama (Gambar 1.5 dan Gambar 1.6).



Ini akan mengubah fungsi formatName, tetapi tidak dalam kasus:



interface Person {
   first: string;
   last: string;
}

const formatName = (p: Person) => `${p.firstName} ${p.last}`;
const formatNameAny = (p: any) => `${p.first} ${p.last}`;




gambar




»Rincian lebih lanjut tentang buku ini dapat ditemukan di situs web penerbit

» Daftar Isi

» Kutipan



Untuk Habitants diskon 25% untuk kupon - TypeScript



Setelah pembayaran untuk versi kertas buku, sebuah e-book dikirim melalui email.



All Articles