Mudah untuk mengembangkan dan menerapkan layanan Java yang sederhana berkat keajaiban Spring Boot yang tersebar luas. Tetapi karena kelas tertutup harus diuji dan data harus diubah, pembangun, konverter, konstruktor enumerasi, dan pembuat serial berlimpah dalam kode, dan membuka jalan untuk Java hell stereotip. Inilah sebabnya mengapa pengembangan fitur baru sering kali tertunda. Dan, ya, pembuatan kode berfungsi, tetapi tidak terlalu fleksibel.
TypeScript belum memantapkan dirinya dengan baik di antara pengembang backend. Mungkin karena ini dikenal sebagai sekumpulan file deklaratif yang memungkinkan Anda menambahkan beberapa pengetikan ke JavaScript. Tapi tetap saja, ada banyak logika yang membutuhkan lusinan baris Java untuk diwakili, dan itu dapat direpresentasikan hanya dalam beberapa baris TypeScript.
Banyak fitur yang dikatakan sebagai karakteristik TypeScript sebenarnya adalah JavaScript. Tetapi TypeScript juga dapat dilihat sebagai bahasanya sendiri, dengan beberapa kesamaan sintaksis dan konseptual dengan JavaScript. Jadi mari kita menyimpang sejenak dari JavaScript dan melihat TypeScript sendiri: ini adalah bahasa yang indah dengan sistem tipe yang sangat kuat namun fleksibel, banyak gula sintaksis, dan akhirnya keamanan nol!
Kami telah meng-host repositori di Github dengan aplikasi web Node / TypeScript yang dibuat khusus, bersama dengan beberapa penjelasan tambahan. Ada juga cabang lanjutan dengan contoh arsitektur bawang dan lebih banyak lagi konsep pengetikan non-sepele.
Memperkenalkan TypeScript
Mari kita mulai dengan dasar-dasarnya: TypeScript adalah bahasa pemrograman fungsional asinkron yang tetap mendukung kelas dan antarmuka, serta atribut publik, pribadi, dan dilindungi. Oleh karena itu, ketika bekerja dengan bahasa ini, seorang programmer memperoleh fleksibilitas yang cukup besar dalam bekerja di tingkat mikroarsitektur dan gaya kode. Compiler TypeScript dapat dikonfigurasi secara dinamis, yaitu mengontrol jenis impor yang diizinkan, jika fungsi memerlukan jenis kembalian eksplisit, dan apakah pemeriksaan null diaktifkan pada waktu kompilasi.
Sejak TypeScript dikompilasi ke JavaScript biasa, Node.js digunakan sebagai runtime backend. Dengan tidak adanya kerangka kerja komprehensif yang menyerupai Spring, layanan web biasa akan menggunakan kerangka kerja yang lebih fleksibel yang berfungsi sebagai server web ( Express.js adalah contoh yang bagus untuk ini ). Oleh karena itu, ini akan menjadi kurang "ajaib", dan pengaturan dasar serta konfigurasi akan diatur lebih eksplisit. Dalam hal ini, layanan yang relatif kompleks juga akan membutuhkan lebih banyak pengaturan dengan konfigurasi. Di sisi lain, menyiapkan aplikasi yang relatif kecil tidaklah sulit, dan, terlebih lagi, hampir dapat dilakukan tanpa mempelajari kerangka kerjanya terlebih dahulu.
Manajemen ketergantungan mudah dilakukan dengan manajer paket Node yang fleksibel namun kuat, npm.
Dasar
Saat menentukan kelas
public, pengubah kontrol akses didukung , protecteddan private, dikenal oleh sebagian besar pengembang:
class Order {
private status: OrderStatus;
constructor(public readonly id: string, isSpecialOrder: boolean) {
[...]
}
}
Kelas sekarang memiliki
Orderdua atribut: statusbidang publik privat dan idhanya-baca. Dalam TypeScript, argumen konstruktor dengan kata kunci public, protectedatau privatesecara otomatis menjadi atribut kelas.
interface User {
id?: string;
name: string;
t_registered: Date;
}
const user: User = { name: 'Bob', t_registered: new Date() };
Perhatikan bahwa karena TypeScript menggunakan inferensi tipe, objek User dapat dibuat instance-nya meskipun kelas
Useritu sendiri tidak tersedia . Ini struktur-seperti pendekatan sering dipilih ketika bekerja dengan entitas data murni dan tidak memerlukan metode atau keadaan internal.
Generik diekspresikan dalam TypeScript dengan cara yang sama seperti di Java:
class Repository<T extends StoredEntity> {
findOneById(id: string): T {
[...]
}
}
Sistem tipe yang kuat
Inti dari sistem tipe TypeScript yang kuat adalah tipe inferensi; itu juga mendukung pengetikan statis. Namun, anotasi tipe statis bersifat opsional jika tipe kembalian atau tipe parameter dapat disimpulkan dari konteks.
TypeScript juga memungkinkan penggunaan tipe gabungan, tipe parsial, dan persimpangan tipe, yang memberikan fleksibilitas yang cukup pada bahasa sambil menghindari kerumitan yang tidak perlu. Di TypeScript, Anda juga dapat menggunakan nilai tertentu sebagai tipe, yang sangat berguna dalam berbagai situasi.
Enumeration, Type Inference, dan Union Type
Pertimbangkan situasi umum di mana status pesanan perlu memiliki representasi tipe-aman (sebagai enumerasi), tetapi juga membutuhkan representasi string untuk serialisasi JSON. Di Java, ini akan menjadi enum, bersama dengan konstruktor dan pengambil untuk nilai string.
Pada contoh pertama, enum TypeScript memungkinkan Anda menambahkan representasi string secara langsung. Ini meninggalkan kita dengan representasi enumerasi tipe-aman yang secara otomatis membuat serial representasi string yang terkait.
enum Status {
ORDER_RECEIVED = 'order_received',
PAYMENT_RECEIVED = 'payment_received',
DELIVERED = 'delivered',
}
interface Order {
status: Status;
}
const order: Order = { status: Status.ORDER_RECEIVED };
Perhatikan baris kode terakhir, di mana inferensi tipe memungkinkan kita untuk membuat instance objek yang cocok dengan antarmuka
`Order`. Karena tidak perlu menempatkan keadaan internal atau logika dalam urutan kita, kita dapat melakukannya tanpa kelas dan tanpa konstruktor.
Benar, ternyata dengan saling berbagi inferensi jenis dan jenis penyatuan, tugas ini dapat diselesaikan lebih mudah:
interface Order {
status: 'order_received' | 'payment_received' | 'delivered';
}
const orderA: Order = { status: 'order_received' }; //
const orderB: Order = { status: 'new' }; //
Compiler TypeScript hanya akan menerima string yang diberikan sebagai status pesanan yang valid (perhatikan bahwa ini masih memerlukan validasi JSON yang masuk).
Pada dasarnya, tipe representasi seperti itu bekerja dengan apapun. Suatu tipe bisa menjadi gabungan dari string literal, angka, dan tipe atau antarmuka kustom lainnya. Untuk contoh yang lebih menarik, lihat Panduan Pengetikan Lanjutan TypeScript .
Lambdas dan argumen fungsional
Karena TypeScript adalah bahasa pemrograman fungsional, ia memiliki dukungan untuk fungsi anonim, juga disebut lambda, pada intinya.
const evenNumbers = [ 1, 2, 3, 4, 5, 6 ].filter(i => i % 2 == 0);
Contoh di atas
.filter()mengambil fungsi tipe (a: T) => boolean. Fungsi ini diwakili oleh lambda anonim i => i % 2 == 0. Tidak seperti Java, di mana parameter fungsional harus memiliki tipe eksplisit, antarmuka fungsional, tipe lambda juga dapat direpresentasikan secara anonim:
class OrderService {
constructor(callback: (order: Order) => void) {
[...]
}
}
Pemrograman asinkron
Karena TypeScript, dengan semua peringatannya, adalah superset dari JavaScript, pemrograman asinkron adalah konsep kunci dalam bahasa ini. Ya, Anda dapat menggunakan lambda dan callback di sini, TypeScript memiliki dua mekanisme penting untuk membantu Anda menghindari neraka callback: janji dan pola cantik
async/await. Promise pada dasarnya adalah nilai pengembalian langsung yang menjanjikan untuk mengembalikan nilai tertentu nanti.
// ,
function fetchUserProfiles(url: string): Promise<UserProfile[]> {
[...]
}
//
function getActiveProfiles(): Promise<UserProfile[]> {
return fetchUserProfiles(URL)
.then(profiles => profiles.filter(profile => profile.active))
.catch(error => handleError(error));
}
Karena instruksi
.then()dapat dirangkai dalam nomor berapa pun, dalam beberapa kasus pola di atas dapat menyebabkan kode yang agak membingungkan. Dengan mendeklarasikan sebuah fungsi asyncdan menggunakannya awaitsambil menunggu promise diselesaikan, Anda dapat menulis kode yang sama ini dengan gaya yang jauh lebih sinkron. Juga dalam hal ini, peluang terbuka untuk menggunakan operator terkenal try/catch:
// async/await ( , fetchUserProfiles )
async function getActiveProfiles(): Promise<UserProfile[]> {
const allProfiles = await fetchUserProfiles(URL);
return allProfiles.filter(profile => profile.active);
}
// try/catch
async function getActiveProfilesSafe(): Promise<UserProfile[]> {
try {
const allProfiles = await fetchUserProfiles(URL);
return allProfiles.filter(profile => profile.active);
} catch (error) {
handleError(error);
return [];
}
}
Perhatikan bahwa meskipun kode di atas tampak sinkron, ini hanya terlihat (karena janji lain dikembalikan di sini).
Operator Ekstensi dan Operator Istirahat: Membuat Hidup Anda Lebih Mudah
Saat menggunakan Java, manipulasi data, konstruksi, penggabungan, dan perusakan objek sering kali menghasilkan kode stereotip dalam jumlah besar. Kelas harus didefinisikan, konstruktor, pengambil dan penyetel harus dibuat, dan objek harus dibuat instance-nya. Dalam kasus pengujian, sering kali diperlukan untuk secara aktif menggunakan refleksi pada contoh tiruan dari kelas tertutup.
Dalam TypeScript, semua ini dapat ditangani dengan mudah dengan memanfaatkan gula sintaksis tipe aman yang manis: operator penyebaran dan operator lain.
Pertama, mari gunakan operator ekspansi array ... untuk mengekstrak array:
const a = [ 'a', 'b', 'c' ];
const b = [ 'd', 'e' ];
const result = [ ...a, ...b, 'f' ];
console.log(result);
// >> [ 'a', 'b', 'c', 'd', 'e', f' ]
Ini nyaman, tentu saja, tetapi TypeScript yang sebenarnya dimulai ketika Anda menyadari bahwa Anda dapat melakukan hal yang sama dengan objek:
interface UserProfile {
userId: string;
name: string;
email: string;
lastUpdated?: Date;
}
interface UserProfileUpdate {
name?: string;
email?: string;
}
const userProfile: UserProfile = { userId: 'abc', name: 'Bob', email: 'bob@example.com' };
const update: UserProfileUpdate = { email: 'bob@example.com' };
const updated: UserProfile = { ...userProfile, ...update, lastUpdated: new Date() };
console.log(updated);
// >> { userId: 'abc', name: 'Bob', email: 'bob@example.com', lastUpdated: 2019-12-19T16:09:45.174Z}
Pertimbangkan apa yang terjadi di sini. Pada dasarnya, sebuah objek
updateddibuat menggunakan konstruktor kurung kurawal. Dalam konstruktor ini, setiap parameter benar-benar membuat objek baru, dimulai dari kiri.
Jadi, objek yang diperluas digunakan
userProfile; hal pertama yang dia lakukan adalah meniru dirinya sendiri. Pada langkah kedua, objek yang diperluas updatedigabungkan ke dalamnya dan ditetapkan kembali ke objek pertama; ini, sekali lagi, membuat objek baru. Pada langkah terakhir, bidang digabungkan dan ditetapkan kembali lastUpdated, lalu objek baru dibuat dan, sebagai hasilnya, objek akhir.
Menggunakan operator penyebaran untuk membuat salinan objek yang tidak dapat diubah adalah cara yang sangat aman dan cepat untuk memproses data. Catatan: Operator penyebaran membuat salinan objek yang dangkal. Elemen dengan kedalaman lebih dari satu kemudian disalin sebagai tautan.
Operator ekstensi juga memiliki penghancur yang setara yang disebut istirahat objek :
const userProfile: UserProfile = { userId: 'abc', name: 'Bob', email: 'bob@example.com' };
const { userId, ...details } = userProfile;
console.log(userId);
console.log(details);
// >> 'abc'
// >> { name: 'Bob', email: 'bob@example.com' }
Sekarang saatnya untuk duduk dan membayangkan semua kode yang harus Anda tulis di Java untuk melakukan operasi seperti yang ditunjukkan di atas.
Kesimpulan. Sedikit tentang kelebihan dan kekurangannya
Performa
Karena TypeScript secara inheren bersifat asinkron dan memiliki lingkungan runtime yang cepat, ada banyak skenario di mana layanan Node / TypeScript dapat bersaing dengan layanan Java. Tumpukan ini sangat bagus untuk operasi I / O dan akan bekerja dengan baik dengan operasi pemblokiran singkat sesekali seperti mengubah ukuran gambar profil baru. Namun, jika tujuan utama sebuah layanan adalah untuk melakukan beberapa komputasi serius pada CPU, Node dan TypeScript mungkin tidak terlalu cocok untuk ini.
Jenis nomor
Jenis yang digunakan di TypeScript juga meninggalkan banyak hal yang diinginkan
number, yang tidak membedakan antara nilai integer dan floating point. Praktik menunjukkan bahwa dalam banyak aplikasi hal ini sama sekali tidak menimbulkan masalah. Namun, sebaiknya tidak menggunakan TypeScript jika Anda menulis aplikasi untuk rekening bank atau layanan checkout.
Ekosistem
Mengingat popularitas Node.js, tidak mengherankan jika ada ratusan ribu paket untuk itu hari ini. Tetapi karena Node lebih muda dari Java, banyak paket yang tidak memiliki banyak versi, dan kualitas kode di beberapa pustaka jelas buruk.
Di antara yang lain, perlu disebutkan beberapa pustaka berkualitas yang sangat nyaman untuk digunakan: misalnya, untuk server web , injeksi ketergantungan , dan anotasi pengontrol . Tetapi, jika layanan akan sangat bergantung pada banyak dan program pihak ketiga yang didukung dengan baik, maka lebih baik menggunakan Python, Java atau Clojure.
Pengembangan fitur yang dipercepat
Seperti yang kita lihat di atas, salah satu keuntungan terpenting dari TypeScript adalah betapa mudahnya mengekspresikan logika, konsep, dan operasi yang rumit dalam bahasa ini. Fakta bahwa JSON adalah bagian integral dari bahasa ini, dan saat ini banyak digunakan sebagai format serialisasi data untuk transfer data dan bekerja dengan database berorientasi dokumen, dalam situasi seperti itu tampaknya wajar untuk menggunakan TypeScript. Menyiapkan server Node sangat cepat, biasanya tanpa ketergantungan yang tidak perlu; ini akan menghemat sumber daya sistem. Inilah sebabnya mengapa kombinasi Node.js dengan sistem tipe kuat TypeScript sangat efektif untuk membuat fitur baru dalam waktu singkat.
Terakhir, TypeScript dibumbui dengan baik dengan gula sintaksis, jadi pengembangan dengannya bagus dan cepat.