Sayangnya, kami harus mengakui bahwa pada tahun 2021 flow sudah jauh lebih rendah daripada TypeScript baik dalam popularitas maupun dukungan dari berbagai utilitas (dan pustaka), dan inilah saatnya untuk
Mengapa Anda membutuhkan keamanan tipe di JavaScript?
JavaScript adalah bahasa yang luar biasa. Tidak, tidak seperti ini. Ekosistem yang dibangun di sekitar JavaScript sangat bagus. Untuk tahun 2021, dia sangat mengagumi fakta bahwa Anda dapat menggunakan fitur paling modern dari bahasa tersebut, dan kemudian, dengan mengubah satu pengaturan sistem build, transpilasikan file yang dapat dieksekusi untuk mendukung pelaksanaannya di versi browser yang lebih lama, termasuk IE8 , itu tidak akan terjadi pada malam hari. Anda dapat "menulis dalam HTML" (artinya JSX), dan kemudian menggunakan utilitas
babel
(atau
tsc
) mengganti semua tag dengan konstruksi JavaScript yang benar seperti memanggil perpustakaan React (atau yang lainnya, tetapi lebih dari itu di posting lain).
Mengapa JavaScript bagus sebagai bahasa skrip yang berjalan di browser Anda?
- JavaScript tidak perlu "dikompilasi". Anda tinggal menambahkan konstruksi JavaScript dan browser harus memahaminya. Ini segera memberikan banyak hal yang nyaman dan hampir gratis. Misalnya, debugging langsung di browser, yang bukan tanggung jawab programmer (yang tidak boleh lupa, misalnya, untuk menyertakan sekumpulan opsi debugging kompiler dan pustaka yang sesuai), tetapi pengembang browser. Anda tidak perlu menunggu 10-30 menit (waktu nyata untuk C / C ++) sementara proyek baris 10k Anda dikompilasi untuk mencoba menulis sesuatu yang berbeda. Anda cukup mengubah baris, memuat ulang halaman browser, dan mengamati perilaku kode yang baru. Dan dalam kasus penggunaan, misalnya, webpack, halaman tersebut juga akan dimuat ulang untuk Anda. Banyak browser mengizinkan Anda untuk mengubah kode langsung di dalam halaman menggunakan alat pengembang mereka.
- - . 2021 . Chrome/Firefox, , , 5% (enterprise-) 30% (UI/) , .
- JavaScript , . — ( worker'). , 100% CPU ( UI ), , , Promise/async/await/etc.
- Pada saat yang sama, saya bahkan tidak mempertimbangkan pertanyaan mengapa JavaScript itu penting. Lagi pula, dengan bantuan JS, Anda dapat: memvalidasi formulir, memperbarui konten halaman tanpa memuat ulang seluruhnya, menambahkan efek perilaku non-standar, bekerja dengan audio dan video, dan Anda bahkan dapat menulis seluruh klien aplikasi perusahaan Anda di JavaScript.
Seperti hampir semua bahasa skrip (ditafsirkan), di JavaScript Anda dapat ... menulis kode yang rusak. Jika browser tidak mencapai kode ini, maka tidak akan ada pesan kesalahan, tidak ada peringatan, tidak ada sama sekali. Di satu sisi, ini bagus. Jika Anda memiliki situs web yang besar dan besar, bahkan kesalahan sintaksis dalam kode pengendali klik tombol tidak akan mengakibatkan situs tidak dimuat sepenuhnya oleh pengguna.
Tapi, tentu saja, ini buruk. Karena fakta bahwa ada sesuatu yang tidak berfungsi di suatu tempat di situs itu buruk. Dan alangkah baiknya, sebelum kode masuk ke situs yang berfungsi, untuk memeriksa semua skrip di situs dan memastikan bahwa setidaknya mereka dapat dikompilasi. Dan idealnya - dan bekerja. Untuk ini, berbagai set utilitas digunakan (set favorit saya adalah npm + webpack + babel / tsc + karma + jsdom + mocha + chai).
Jika kita hidup di dunia yang ideal, maka semua-semua skrip di situs Anda, bahkan yang satu baris, dicakup dengan tes. Namun, sayangnya, dunia ini tidak ideal, dan untuk semua bagian kode yang tidak tercakup dalam pengujian, kami hanya dapat mengandalkan beberapa jenis alat verifikasi otomatis. Yang dapat memeriksa:
- JavaScript. , JavaScript, , , . /// .
- . , , . , :
var x = null; x.foo();
. — null .
Selain kesalahan semantik, bisa jadi ada kesalahan yang lebih mengerikan: kesalahan logika. Ketika program berjalan tanpa error, tapi hasilnya sama sekali tidak seperti yang diharapkan. Klasik dengan tambahan string dan angka:
console.log( input.value ) // 1
console.log( input.value + 1 ) // 11
Alat analisis kode statis yang ada (eslint, misalnya) dapat mencoba melacak sejumlah besar potensi kesalahan yang dibuat oleh pemrogram dalam kodenya. Sebagai contoh:
- Melarang pengulangan for tak terhingga dengan kondisi penghentian pengulangan yang salah
- Melarang Fungsi Asinkron sebagai Argumen untuk Pembuat Janji
- Melarang Tugas dalam Kondisi
- dan lainnya
Perhatikan bahwa semua aturan ini pada dasarnya adalah batasan yang ditempatkan linter pada pemrogram. Artinya, linter sebenarnya mengurangi kemampuan bahasa JavaScript sehingga pemrogram membuat lebih sedikit potensi kesalahan. Jika Anda mengaktifkan semua-semua aturan, maka tidak mungkin membuat penetapan dalam kondisi (meskipun JavaScript awalnya mengizinkan ini), menggunakan kunci duplikat dalam literal objek, dan bahkan tidak dapat dipanggil
console.log()
.
Menambahkan jenis variabel dan pemeriksaan jenis panggilan adalah batasan tambahan dari bahasa JavaScript untuk mengurangi potensi kesalahan.
Mencoba mengalikan angka dengan string
Mencoba mengakses properti yang tidak ada (tidak dijelaskan dalam tipe) dari suatu objek.
Upaya untuk memanggil fungsi dengan tipe argumen yang tidak cocok.
Jika kita menulis kode ini tanpa pemeriksa tipe, kode tersebut berhasil ditranspilasi. Tidak ada cara analisis kode statis, jika mereka tidak menggunakan (secara eksplisit atau implisit) informasi tentang jenis objek, tidak akan dapat menemukan kesalahan ini.
Artinya, menambahkan pengetikan ke JavaScript menambahkan batasan tambahan ke kode yang ditulis oleh pemrogram, tetapi memungkinkan Anda menemukan kesalahan yang seharusnya terjadi selama eksekusi skrip (kemungkinan besar di browser pengguna).
Kemampuan mengetik JavaScript
| Mengalir | TypeScript | |
|---|---|---|
| Kemampuan untuk mengatur tipe variabel, argumen, atau tipe kembalian dari suatu fungsi | |
|
| Kemampuan untuk mendeskripsikan tipe objek Anda (antarmuka) | |
|
| Membatasi Nilai untuk suatu Jenis | |
|
| Pisahkan ekstensi tingkat jenis untuk pencacahan |
|
|
| "Menambahkan" jenis |
|
|
| "Jenis" tambahan untuk kasus yang kompleks |
|
|
Kedua mesin untuk dukungan jenis JavaScript memiliki kemampuan yang kurang lebih sama. Namun, jika Anda berasal dari bahasa yang sangat diketik, bahkan JavaScript yang diketik memiliki perbedaan yang sangat penting dari Java: semua tipe pada dasarnya mendeskripsikan antarmuka, yaitu daftar properti (dan tipe dan / atau argumennya). Dan jika dua antarmuka mendeskripsikan properti yang sama (atau kompatibel), maka keduanya dapat digunakan sebagai pengganti satu sama lain. Artinya, kode berikut benar dalam JavaScript yang diketik, tetapi jelas salah di Java, atau, katakanlah, C ++:
type MyTypeA = { foo: string; bar: number; } type MyTypeB = { foo: string; } function myFunction( arg : MyTypeB ) : string { return `Hello, ${arg.foo}!`; } const myVar : MyTypeA = { foo: "World", bar: 42 } as MyTypeA; console.log( myFunction( myVar ) ); // "Hello, World!"
Kode ini benar dari sudut pandang JavaScript yang diketik, karena antarmuka MyTypeB memerlukan properti
foo
dengan tipe
string
, sedangkan variabel dengan antarmuka MyTypeA membutuhkannya.
Kode ini dapat ditulis ulang sedikit lebih pendek, menggunakan antarmuka literal untuk variabel
myVar
.
type MyTypeB = { foo: string; } function myFunction( arg : MyTypeB ) : string { return `Hello, ${arg.foo}!`; } const myVar = { foo: "World", bar: 42 }; console.log( myFunction( myVar ) ); // "Hello, World!"
Jenis variabel
myVar
dalam contoh ini adalah antarmuka literal
{ foo: string, bar: number }
. Ini masih kompatibel dengan antarmuka yang diharapkan dari argumen
arg
fungsi
myFunction
, jadi kode ini bebas kesalahan dari sudut pandang, misalnya, TypeScript.
Perilaku ini secara signifikan mengurangi jumlah masalah saat bekerja dengan pustaka yang berbeda, kode kustom, dan bahkan hanya memanggil fungsi. Contoh tipikal adalah ketika beberapa pustaka mendefinisikan opsi yang valid, dan kami meneruskannya sebagai objek opsi:
// - interface OptionsType { optionA?: string; optionB?: number; } export function libFunction( arg: number, options = {} as OptionsType) { /*...*/ }
// import {libFunction} from "lib"; libFunction( 42, { optionA: "someValue" } );
Perhatikan bahwa tipe
OptionsType
tidak diekspor dari pustaka (juga tidak diimpor ke kode kustom). Tapi ini tidak mencegah Anda memanggil fungsi menggunakan antarmuka literal untuk argumen kedua dari
options
fungsi tersebut, dan untuk sistem pengetikan - untuk memeriksa argumen ini untuk kompatibilitas tipe. Mencoba melakukan sesuatu seperti ini di Java akan menyebabkan kebingungan yang jelas di antara compiler.
Bagaimana cara kerjanya dari perspektif browser?
Baik TypeScript maupun aliran Facebook dari Microsoft tidak didukung oleh browser. Serta ekstensi bahasa JavaScript terbaru belum menemukan dukungan di beberapa browser. Jadi bagaimana kode ini, pertama, diperiksa kebenarannya, dan kedua, bagaimana itu dieksekusi oleh browser?
Jawabannya menjebak. Semua kode JavaScript "non-standar" melewati sekumpulan utilitas yang mengubah kode "non-standar" (tidak diketahui browser) menjadi sekumpulan petunjuk yang dipahami browser. Dan untuk mengetik, seluruh "transformasi" terdiri dari fakta bahwa semua perbaikan tipe, semua deskripsi antarmuka, semua batasan dari kode akan dihapus begitu saja. Misal kode dari contoh diatas berubah menjadi ...
/* : type MyTypeA = { foo: string; bar: number; } */ /* : type MyTypeB = { foo: string; } */ function myFunction( arg /* : : MyTypeB */ ) /* : : string */ { return `Hello, ${arg.foo}!`; } const myVar /* : : MyTypeA */ = { foo: "World", bar: 42 } /* : as MyTypeA */; console.log( myFunction( myVar ) ); // "Hello, World!"
itu.
function myFunction( arg ) { return `Hello, ${arg.foo}!`; } const myVar = { foo: "World", bar: 42 }; console.log( myFunction( myVar ) ); // "Hello, World!"
Konversi ini biasanya dilakukan dengan salah satu cara berikut.
- Untuk menghapus informasi tipe dari flow, plugin babel digunakan: @ babel / plugin-transform-flow-strip-types
- Anda dapat menggunakan salah satu dari dua solusi untuk bekerja dengan TypeScript. Pertama, Anda dapat menggunakan babel dan plugin @ babel / plugin-transform-typescript
- Kedua, alih-alih babel, Anda dapat menggunakan transpiler milik Microsoft yang disebut tsc . Utilitas ini dibangun ke dalam proses pembuatan aplikasi, bukan babel.
Contoh pengaturan proyek untuk aliran dan untuk TypeScript (menggunakan tsc).
| Mengalir | TypeScript |
|---|---|
| webpack.config.js | |
|
|
| Pengaturan Transpiler | |
| babel.config.js | tsconfig.json |
|
|
| .flowconfig | |
|
|
Perbedaan antara pendekatan babel + strip dan tsc kecil dalam hal perakitan. Dalam kasus pertama, babel digunakan, dalam kasus kedua, itu akan menjadi tsc.
Tetapi ada perbedaan jika utilitas seperti eslint digunakan. TypeScript untuk linting dengan eslint memiliki kumpulan pluginnya sendiri yang memungkinkan Anda menemukan lebih banyak bug. Tetapi mereka mensyaratkan bahwa pada saat analisis oleh linter, ia memiliki informasi tentang jenis variabel. Untuk melakukan ini, hanya tsc yang harus digunakan sebagai pengurai kode, bukan babel. Tetapi jika tsc digunakan untuk linter, maka akan salah jika menggunakan babel untuk bangunan (kebun binatang utilitas yang digunakan harus minimal!).
| Mengalir | TypeScript |
|---|---|
| .eslint.js | |
|
|
Jenis untuk perpustakaan
Ketika perpustakaan diterbitkan ke repositori npm, itu adalah versi JavaScript yang diterbitkan. Diasumsikan bahwa kode yang diterbitkan tidak perlu dimodifikasi untuk digunakan dalam proyek. Artinya, kode tersebut telah melewati traspilasi yang diperlukan melalui babel atau tsc. Tapi kemudian informasi tentang jenis kode tersebut sudah hilang. Apa yang harus dilakukan?
Dalam alirannya, diasumsikan bahwa selain versi JavaScript "murni", pustaka akan berisi file dengan ekstensi
.js.flow
berisi kode aliran sumber dengan semua definisi tipe. Kemudian, saat menganalisis aliran, ia akan dapat menghubungkan file-file ini untuk pemeriksaan tipe, dan ketika membangun proyek dan pelaksanaannya, mereka akan diabaikan - file JS biasa akan digunakan. Anda dapat menambahkan file .flow ke pustaka dengan menyalin sederhana. Namun, ini secara signifikan akan meningkatkan ukuran pustaka di npm.
Dalam TypeScript, tidak disarankan untuk menyimpan file sumber secara berdampingan, tetapi hanya daftar definisi. Jika ada file
myModule.js
, maka ketika menganalisis proyek, TypeScript akan mencari file di dekatnya
myModule.js.d.ts
, di mana ia mengharapkan untuk melihat definisi (tetapi bukan kode!) Dari semua jenis, fungsi, dan hal lain yang diperlukan untuk menganalisis jenis. Transpiler tsc dapat membuat file seperti itu dari TypeScript sendiri (lihat opsi
declaration
dalam dokumentasi).
Jenis untuk pustaka warisan
Untuk aliran dan TypeScript, ada cara untuk menambahkan deklarasi tipe untuk pustaka yang awalnya tidak berisi deskripsi ini. Tapi itu dilakukan dengan cara yang berbeda.
Untuk aliran, tidak ada metode "asli" yang didukung oleh Facebook itu sendiri. Tapi ada proyek tipe aliran yang mengumpulkan definisi seperti itu dalam repositori. Sebenarnya, cara paralel untuk npm ke versi definisi seperti itu, dan juga tidak terlalu nyaman "terpusat" untuk memperbarui.
Di TypeScript, cara standar untuk menulis definisi tersebut adalah dengan menerbitkannya dalam paket npm khusus dengan awalan "@types"... Untuk menambahkan deskripsi tipe untuk sebuah perpustakaan ke proyek Anda, itu cukup untuk menghubungkan @ types-library yang sesuai, misalnya,
@types/react
untuk React atau
@types/chai
untuk chai.
Perbandingan aliran dan TypeScript
Upaya untuk membandingkan aliran dan TypeScript. Fakta yang dipilih dikumpulkan dari artikel Nathan Sebhastian "TypeScript VS Flow", beberapa dikumpulkan secara independen.
Dukungan asli di berbagai kerangka kerja. Asli - tidak ada pendekatan tambahan dengan besi solder serta pustaka dan plugin pihak ketiga.
Berbagai penguasa
| Mengalir | TypeScript | |
|---|---|---|
| Kontributor utama | Microsoft | |
| Situs web | flow.org | www.typescriptlang.org |
| Github | github.com/facebook/flow | github.com/microsoft/TypeScript |
| GitHub Dimulai | 21.3k | 70.1k |
| Garpu GitHub | 1.8k | 9.2k |
| Masalah GitHub: buka / tutup | 2,4k / 4,1k | 4,9k / 25,0k |
| StackOverflow Aktif | 2289 | 146.221 |
| StackOverflow Sering | 123 | 11451 |
Melihat angka-angka ini, saya sama sekali tidak memiliki hak moral untuk merekomendasikan aliran untuk digunakan. Tetapi mengapa saya menggunakannya sendiri? Karena dulu ada yang namanya aliran-runtime.
flow-runtime
flow-runtime adalah sekumpulan plugin untuk babel yang memungkinkan Anda menyematkan jenis aliran ke dalam runtime, menggunakannya untuk menentukan jenis variabel pada waktu proses, dan, yang terpenting bagi saya, memungkinkan Anda untuk memeriksa jenis variabel pada waktu proses. Itu memungkinkan pada waktu proses selama, misalnya, pengujian otomatis atau pengujian manual, untuk menangkap bug tambahan dalam aplikasi.
Artinya, tepat pada waktu proses (dalam rakitan debug, tentu saja), aplikasi secara eksplisit memeriksa semua jenis variabel, argumen, hasil panggilan ke fungsi pihak ketiga, dan semuanya, semuanya, semuanya, untuk kepatuhan dengan jenis tersebut.
Sayangnya, untuk tahun baru 2021, penulis repositori menambahkan informasibahwa dia tidak lagi terlibat dalam pengembangan proyek ini dan, secara umum, beralih ke TypeScript. Faktanya, alasan terakhir untuk tetap mengikuti arus menjadi tidak berlaku lagi bagi saya. Selamat datang di TypeScript.