TypeScript vs. JavaScript: Panduan Masuk Anda
Diterbitkan: 2022-03-11TypeScript atau JavaScript? Pengembang mempertimbangkan pilihan ini untuk proyek web greenfield atau Node.js, tetapi ini juga merupakan pertanyaan yang layak dipertimbangkan untuk proyek yang sudah ada. Sebuah superset dari JavaScript, TypeScript menawarkan semua fitur JavaScript ditambah beberapa fasilitas tambahan. TypeScript secara intrinsik mendorong kita untuk membuat kode dengan bersih, membuat kode lebih terukur. Namun, proyek dapat berisi JavaScript biasa sebanyak yang kita suka, jadi menggunakan TypeScript bukanlah proposisi semua atau tidak sama sekali.
Hubungan Antara TypeScript dan JavaScript
TypeScript menambahkan sistem tipe eksplisit ke JavaScript, memungkinkan penegakan tipe variabel yang ketat. TypeScript menjalankan pemeriksaan tipenya saat transpiling —suatu bentuk kompilasi yang mengubah kode TypeScript ke browser web kode JavaScript dan dipahami oleh Node.js.
Contoh TypeScript vs. JavaScript
Mari kita mulai dengan cuplikan JavaScript yang valid:
let var1 = "Hello"; var1 = 10; console.log(var1); Di sini, var1 dimulai sebagai string , kemudian menjadi number .
Karena JavaScript hanya diketik secara longgar, kita dapat mendefinisikan ulang var1 sebagai variabel jenis apa pun—dari string hingga fungsi—kapan saja.
Menjalankan kode ini menghasilkan 10 .
Sekarang, mari kita ubah kode ini menjadi TypeScript:
let var1: string = "Hello"; var1 = 10; console.log(var1); Dalam hal ini, kami mendeklarasikan var1 menjadi string . Kami kemudian mencoba memberikan nomor padanya, yang tidak diizinkan oleh sistem tipe ketat TypeScript. Transpiling menghasilkan kesalahan:
TSError: ⨯ Unable to compile TypeScript: src/snippet1.ts:2:1 - error TS2322: Type 'number' is not assignable to type 'string'. 2 var1 = 10; Jika kita menginstruksikan transpiler untuk memperlakukan cuplikan JavaScript asli seolah-olah itu adalah TypeScript, transpiler akan secara otomatis menyimpulkan bahwa var1 harus berupa string | number string | number . Ini adalah tipe serikat TypeScript , yang memungkinkan kita untuk menetapkan var1 sebuah string atau number kapan saja. Setelah menyelesaikan konflik jenis, kode TypeScript kami akan berhasil diubah. Menjalankannya akan menghasilkan hasil yang sama seperti contoh JavaScript.
TypeScript vs. JavaScript Dari 30.000 Kaki: Tantangan Skalabilitas
JavaScript ada di mana-mana, mendukung proyek dari semua ukuran, diterapkan dengan cara yang tidak terbayangkan selama masa pertumbuhannya di tahun 1990-an. Meskipun JavaScript telah matang, ia gagal dalam hal dukungan skalabilitas. Oleh karena itu, para pengembang bergulat dengan aplikasi JavaScript yang telah berkembang baik dalam hal besaran maupun kompleksitasnya.
Untungnya, TypeScript mengatasi banyak masalah penskalaan proyek JavaScript. Kami akan fokus pada tiga tantangan teratas: validasi, pemfaktoran ulang, dan dokumentasi.
Validasi
Kami mengandalkan lingkungan pengembangan terintegrasi (IDE) untuk membantu tugas-tugas seperti menambahkan, memodifikasi, dan menguji kode baru, tetapi IDE tidak dapat memvalidasi referensi JavaScript murni. Kami mengurangi kekurangan ini dengan memantau dengan cermat saat kami membuat kode untuk mencegah kemungkinan kesalahan ketik dalam variabel dan nama fungsi.
Besarnya masalah tumbuh secara eksponensial ketika kode berasal dari pihak ketiga, di mana referensi yang rusak di cabang kode yang jarang dieksekusi dapat dengan mudah tidak terdeteksi.
Sebaliknya, dengan TypeScript, kami dapat memfokuskan upaya kami pada pengkodean, yakin bahwa kesalahan apa pun akan diidentifikasi pada waktu transpile. Untuk mendemonstrasikan ini, mari kita mulai dengan beberapa kode JavaScript lawas:
const moment = require('moment'); const printCurrentTime = (format) => { if (format === 'ISO'){ console.log("Current ISO TS:", moment().toISO()); } else { console.log("Current TS: ", moment().format(format)); } } .toISO() adalah kesalahan ketik metode moment.js toISOString() tetapi kode akan berfungsi, asalkan argumen format bukan ISO . Pertama kali kita mencoba meneruskan ISO ke fungsi, itu akan memunculkan kesalahan runtime ini: TypeError: moment(...).toISO is not a function .
Menemukan kode yang salah eja mungkin sulit. Basis kode saat ini mungkin tidak memiliki jalur ke garis putus-putus, dalam hal ini referensi .toISO() kami yang rusak tidak akan ditangkap oleh pengujian.
Jika kita mem-porting kode ini ke TypeScript, IDE akan menyorot referensi yang rusak, mendorong kita untuk melakukan koreksi. Jika kami tidak melakukan apa pun dan mencoba melakukan transpile, kami akan diblokir, dan transpiler akan menghasilkan kesalahan berikut:
TSError: ⨯ Unable to compile TypeScript: src/catching-mistakes-at-compile-time.ts:5:49 - error TS2339: Property 'toISO' does not exist on type 'Moment'. 5 console.log("Current ISO TS:", moment().toISO());Pemfaktoran ulang
Meskipun kesalahan ketik dalam referensi kode pihak ketiga tidak jarang terjadi, ada serangkaian masalah yang terkait dengan kesalahan ketik dalam referensi internal, seperti ini:
const myPhoneFunction = (opts) => { // ... if (opts.phoneNumbr) doStuff(); } Pengembang tunggal dapat menemukan dan memperbaiki semua instance phoneNumbr untuk diakhiri dengan er dengan cukup mudah.
Tetapi semakin besar tim, semakin sederhana, kesalahan umum ini sangat mahal. Dalam melakukan pekerjaan mereka, rekan kerja perlu menyadari dan menyebarkan kesalahan ketik tersebut. Atau, menambahkan kode untuk mendukung kedua ejaan akan menggembungkan basis kode yang tidak perlu.
Dengan TypeScript, saat kami memperbaiki kesalahan ketik, kode dependen tidak akan berubah lagi, menandakan rekan kerja untuk menyebarkan perbaikan ke kode mereka.
Dokumentasi
Dokumentasi yang akurat dan relevan adalah kunci komunikasi di dalam dan di antara tim pengembang. Pengembang JavaScript sering menggunakan JSdoc untuk mendokumentasikan metode dan tipe properti yang diharapkan.
Fitur bahasa TypeScript (misalnya, kelas abstrak, antarmuka, dan definisi tipe) memfasilitasi pemrograman desain-dengan-kontrak, yang mengarah ke dokumentasi berkualitas. Selain itu, memiliki definisi formal tentang metode dan properti yang harus dipatuhi oleh objek akan membantu mengidentifikasi perubahan yang melanggar, membuat pengujian, melakukan introspeksi kode, dan mengimplementasikan pola arsitektur.
Untuk TypeScript, alat masuk TypeDoc (berdasarkan proposal TSdoc) secara otomatis mengekstrak informasi jenis (misalnya, kelas, antarmuka, metode, dan properti) dari kode kita. Jadi, kami dengan mudah membuat dokumentasi yang, sejauh ini, lebih komprehensif daripada JSdoc.
Keuntungan TypeScript vs. JavaScript
Sekarang, mari kita jelajahi bagaimana kita dapat menggunakan TypeScript untuk mengatasi tantangan skalabilitas ini.
Kode Lanjutan/Saran Pemfaktoran Ulang
Banyak IDE dapat memproses informasi dari sistem tipe TypeScript, memberikan validasi referensi saat kami membuat kode. Lebih baik lagi, saat kita mengetik, IDE dapat memberikan dokumentasi sekilas yang relevan (misalnya, argumen yang diharapkan fungsi) untuk referensi apa pun dan menyarankan nama variabel yang benar secara kontekstual.
Dalam cuplikan TypeScript ini, IDE menyarankan pelengkapan otomatis nama-nama kunci di dalam nilai kembalian fungsi:
/** * Simple function to parse a CSV containing people info. * @param data A string containing a CSV with 3 fields: name, surname, age. */ const parsePeopleData = (data: string) => { const people: {name: string, surname: string, age: number}[] = []; const errors: string[] = []; for (let row of data.split('\n')){ if (row.trim() === '') continue; const tokens = row.split(',').map(i => i.trim()).filter(i => i != ''); if (tokens.length < 3){ errors.push(`Row "${row}" contains only ${tokens.length} tokens. 3 required`); continue; } people.push({ name: tokens[0], surname: tokens[1], age: +tokens[2] }) } return {people, errors}; }; const exampleData = ` Gordon,Freeman,27 G,Man,99 Alyx,Vance,24 Invalid Row,, Again, Invalid `; const result = parsePeopleData(exampleData); console.log("Parsed People:"); console.log(result.people. map(p => `Name: ${p.name}\nSurname: ${p.surname}\nAge: ${p.age}`) .join('\n\n') ); if (result.errors.length > 0){ console.log("\nErrors:"); console.log(result.errors.join('\n')); }IDE saya, Visual Studio Code, memberikan saran ini (dalam info) ketika saya mulai memanggil fungsi (baris 31):
Terlebih lagi, saran pelengkapan otomatis IDE (dalam info) benar secara kontekstual, hanya menampilkan nama yang valid dalam situasi kunci bersarang (baris 34):
Saran waktu nyata seperti itu menghasilkan pengkodean yang lebih cepat. Selain itu, IDE dapat mengandalkan informasi jenis TypeScript yang ketat untuk memfaktorkan ulang kode pada skala apa pun. Operasi seperti mengganti nama properti, mengubah lokasi file, atau bahkan mengekstrak superclass menjadi sepele ketika kami 100% yakin dengan keakuratan referensi kami.

Dukungan Antarmuka
Berbeda dengan JavaScript, TypeScript menawarkan kemampuan untuk mendefinisikan tipe menggunakan interfaces . Antarmuka secara formal mencantumkan—tetapi tidak mengimplementasikan—metode dan properti yang harus disertakan oleh objek. Konstruksi bahasa ini sangat membantu untuk kolaborasi dengan pengembang lain.
Contoh berikut menyoroti bagaimana kita dapat memanfaatkan fitur TypeScript untuk menerapkan pola OOP umum dengan rapi—dalam hal ini, strategi dan rantai tanggung jawab —sehingga meningkatkan contoh sebelumnya:
export class PersonInfo { constructor( public name: string, public surname: string, public age: number ){} } export interface ParserStrategy{ /** * Parse a line if able. * @returns The parsed line or null if the format is not recognized. */ (line: string): PersonInfo | null; } export class PersonInfoParser{ public strategies: ParserStrategy[] = []; parse(data: string){ const people: PersonInfo[] = []; const errors: string[] = []; for (let row of data.split('\n')){ if (row.trim() === '') continue; let parsed; for (let s of this.strategies){ parsed = s(row); if (parsed) break; } if (!parsed){ errors.push(`Unable to find a strategy capable of parsing "${row}"`); } else { people.push(parsed); } } return {people, errors}; } } const exampleData = ` Gordon,Freeman,27 G;Man;99 {"name":"Alyx", "surname":"Vance", "age":24} Invalid Row,, Again, Invalid `; const parser = new PersonInfoParser(); const createCSVStrategy = (fieldSeparator = ','): ParserStrategy => (line) => { const tokens = line.split(fieldSeparator).map(i => i.trim()).filter(i => i != ''); if (tokens.length < 3) return null; return new PersonInfo(tokens[0], tokens[1], +tokens[2]); }; parser.strategies.push( (line) => { try { const {name, surname, age} = JSON.parse(line); return new PersonInfo(name, surname, age); } catch(err){ return null; } }, createCSVStrategy(), createCSVStrategy(';') ); const result = parser.parse(exampleData); console.log("Parsed People:"); console.log(result.people. map(p => `Name: ${p.name}\nSurname: ${p.surname}\nAge: ${p.age}`) .join('\n\n') ); if (result.errors.length > 0){ console.log("\nErrors:"); console.log(result.errors.join('\n')); }Modul ES6—Di Mana Saja
Pada tulisan ini, tidak semua runtime JavaScript front-end dan back-end mendukung modul ES6. Namun, dengan TypeScript, kita dapat menggunakan sintaks modul ES6:
import * as _ from 'lodash'; export const exampleFn = () => console.log(_.reverse(['a', 'b', 'c'])); Output transpiled akan kompatibel dengan lingkungan yang kita pilih. Misalnya, menggunakan opsi kompiler --module CommonJS , kita mendapatkan:
"use strict"; exports.__esModule = true; exports.exampleFn = void 0; var _ = require("lodash"); var exampleFn = function () { return console.log(_.reverse(['a', 'b', 'c'])); }; exports.exampleFn = exampleFn; Menggunakan --module UMD sebagai gantinya, TypeScript mengeluarkan pola UMD yang lebih verbose:
(function (factory) { if (typeof module === "object" && typeof module.exports === "object") { var v = factory(require, exports); if (v !== undefined) module.exports = v; } else if (typeof define === "function" && define.amd) { define(["require", "exports", "lodash"], factory); } })(function (require, exports) { "use strict"; exports.__esModule = true; exports.exampleFn = void 0; var _ = require("lodash"); var exampleFn = function () { return console.log(_.reverse(['a', 'b', 'c'])); }; exports.exampleFn = exampleFn; });Kelas ES6—Di Mana Saja
Lingkungan lama sering kali kekurangan dukungan untuk kelas ES6. Transpile TypeScript memastikan kompatibilitas dengan menggunakan konstruksi target-spesifik. Berikut cuplikan sumber TypeScript:
export class TestClass { hello = 'World'; }Output JavaScript bergantung pada modul dan target, yang TypeScript memungkinkan kita tentukan.
Inilah yang --module CommonJS --target es3 :
"use strict"; exports.__esModule = true; exports.TestClass = void 0; var TestClass = /** @class */ (function () { function TestClass() { this.hello = 'World'; } return TestClass; }()); exports.TestClass = TestClass; Menggunakan --module CommonJS --target es6 sebagai gantinya, kami mendapatkan hasil transpilasi berikut. Kata kunci class digunakan untuk menargetkan ES6:
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.TestClass = void 0; class TestClass { constructor() { this.hello = 'World'; } } exports.TestClass = TestClass;Fungsionalitas Async/Menunggu—Di Mana Saja
Async/await membuat kode JavaScript asinkron lebih mudah dipahami dan dipelihara. TypeScript menawarkan fungsionalitas ini ke semua runtime, bahkan untuk runtime yang tidak menyediakan async/menunggu secara native.
Perhatikan bahwa untuk menjalankan async/menunggu pada runtime yang lebih lama seperti ES3 dan ES5, Anda memerlukan dukungan eksternal untuk keluaran berbasis Promise (misalnya, melalui Bluebird atau polyfill ES2015). Polyfill Promise yang dikirimkan dengan TypeScript terintegrasi dengan mudah ke dalam output yang ditranspilasikan—kita hanya perlu mengonfigurasi opsi kompiler lib yang sesuai.
Dukungan untuk Bidang Kelas Privat—Di Mana Saja
Bahkan untuk target lama, TypeScript mendukung bidang private dengan cara yang hampir sama seperti bahasa yang diketik dengan kuat (misalnya, Java atau C#). Sebaliknya, banyak runtime JavaScript mendukung bidang private melalui sintaks awalan hash, yang merupakan proposal ES2022 yang sudah selesai.
Kekurangan TypeScript vs. JavaScript
Sekarang kita telah menyoroti manfaat utama dari penerapan TypeScript, mari kita jelajahi skenario di mana TypeScript mungkin tidak cocok.
Transpilasi: Potensi Ketidakcocokan Alur Kerja
Alur kerja atau persyaratan proyek tertentu mungkin tidak sesuai dengan langkah transpilasi TypeScript: misalnya, jika kita perlu menggunakan alat eksternal untuk mengubah kode setelah penerapan atau jika output yang dihasilkan harus ramah pengembang.
Misalnya, saya baru-baru ini menulis fungsi AWS Lambda untuk lingkungan Node.js. TypeScript tidak cocok karena memerlukan transpilasi akan mencegah saya, dan anggota tim lainnya, mengedit fungsi menggunakan editor online AWS. Ini adalah pemecah kesepakatan untuk manajer proyek.
Jenis Sistem Bekerja Hanya Sampai Waktu Transpile
Output JavaScript TypeScript tidak berisi informasi tipe, sehingga tidak akan melakukan pemeriksaan tipe dan, oleh karena itu, keamanan tipe dapat rusak saat runtime. Misalnya, misalkan suatu fungsi didefinisikan untuk selalu mengembalikan objek. Jika null dikembalikan dari penggunaannya dalam file .js , kesalahan runtime akan terjadi.
Ketik fitur yang bergantung pada informasi (misalnya, bidang pribadi, antarmuka, atau generik) menambah nilai untuk setiap proyek tetapi tergores saat transpiling. Misalnya, anggota kelas private tidak lagi menjadi pribadi setelah transpilasi. Untuk lebih jelasnya, masalah runtime seperti ini tidak unik untuk TypeScript, dan Anda juga dapat menghadapi kesulitan yang sama dengan JavaScript.
Menggabungkan TypeScript dan JavaScript
Terlepas dari banyak manfaat TypeScript, terkadang kami tidak dapat membenarkan mengonversi seluruh proyek JavaScript sekaligus. Untungnya, kita dapat menentukan transpiler TypeScript—berdasarkan file demi file—apa yang harus ditafsirkan sebagai JavaScript biasa. Faktanya, pendekatan hibrida ini dapat membantu mengurangi tantangan individu yang muncul selama siklus hidup proyek.
Kami mungkin lebih suka membiarkan JavaScript tidak berubah jika kodenya:
- Ditulis oleh mantan kolega dan akan membutuhkan upaya rekayasa balik yang signifikan untuk mengonversi ke TypeScript.
- Menggunakan teknik yang tidak diizinkan dalam TypeScript (misalnya, menambahkan properti setelah pembuatan objek) dan akan memerlukan pemfaktoran ulang untuk mematuhi aturan TypeScript.
- Milik tim lain yang terus menggunakan JavaScript.
Dalam kasus seperti itu, file deklarasi ( file .d.ts , terkadang disebut file definisi atau file pengetikan) memberikan TypeScript data tipe yang cukup untuk mengaktifkan saran IDE sambil membiarkan kode JavaScript apa adanya.
Banyak perpustakaan JavaScript (misalnya, Lodash, Jest, dan React) menyediakan file pengetikan TypeScript dalam paket tipe terpisah, sementara yang lain (misalnya, Moment.js, Axios, dan Luxon) mengintegrasikan file pengetikan ke dalam paket utama.
TypeScript vs. JavaScript: Pertanyaan tentang Perampingan dan Skalabilitas
Dukungan, fleksibilitas, dan peningkatan tak tertandingi yang tersedia melalui TypeScript secara signifikan meningkatkan pengalaman pengembang, memungkinkan proyek dan tim untuk berkembang. Biaya utama untuk memasukkan TypeScript ke dalam sebuah proyek adalah penambahan langkah pembuatan transpilasi. Untuk sebagian besar aplikasi, transpiling ke JavaScript tidak menjadi masalah; alih-alih, ini adalah batu loncatan untuk banyak manfaat TypeScript.
Bacaan Lebih Lanjut di Blog Teknik Toptal:
- Bekerja Dengan TypeScript dan Dukungan Jest: Tutorial AWS SAM
