Membuat TypeScript lebih ketat. Laporan Yandex

Bagaimana cara membuat TypeScript pendamping yang ketat tapi adil yang akan melindungi Anda dari bug jahat dan memberi Anda kepercayaan lebih pada kode Anda? Alexey Veselovsky veselovskiyaimempertimbangkan beberapa fitur konfigurasi TS yang menutup mata terhadap kebebasan yang tak termaafkan. Laporan tersebut menguraikan hal-hal yang sebaiknya dihindari dan hal-hal yang perlu Anda waspadai. Anda akan belajar tentang pustaka io-ts yang luar biasa - ini memungkinkan Anda dengan mudah mendeteksi dan bahkan mencegah data memasukkan kode yang dapat menyebabkan kesalahan di tempat yang ditulis dengan sempurna.



- Halo semuanya, nama saya Lesha, saya adalah pengembang frontend. Ayo mulai. Saya akan menceritakan sedikit tentang diri saya dan proyek tempat saya bekerja. Flow belajar bahasa Inggris dari Yandex.Practicum. Perilisan berlangsung pada bulan April tahun ini. Bagian depannya ditulis langsung dalam TypeScript, sebelumnya tidak ada kode.







Sedikit tentang pengalaman saya. Dalam beberapa tahun yang jauh, saya mulai membuat program. Pada 2013, dia mulai bekerja.







Hampir segera saya menyadari bahwa saya jauh lebih tertarik pada bagian depan, tetapi saya memiliki pengalaman dengan bahasa yang diketik secara statis. Saya mulai menggunakan JavaScript dan pengetikan statis ini tidak ada. Bagiku itu nyaman, aku menyukainya.



Pada perubahan proyek, saya mulai bekerja menggunakan TypeScript. Saya akan memberi tahu Anda tentang kelebihan yang saya sadari dengan beralih ke TypeScript. Lebih mudah untuk memahami proyek. Kami memiliki deskripsi tipe data yang digunakan dalam proyek dan konversi di antara mereka.







Lebih aman untuk membuat perubahan pada kode: jika ada perubahan pada backend atau hanya beberapa bagian dari kode, TypeScript akan menyoroti tempat-tempat munculnya kesalahan.



Kekhawatiran tentang jenisnya kurang. Saat kami membuat fungsionalitas baru, kami segera mengatur jenis yang berfungsi, dan kami tidak perlu khawatir bahwa kami akan menerima data yang berbeda.



Tidak ada ketakutan bahwa null atau undefined akan datang, kita tidak perlu menjadi paranoid, memasukkan konstruksi yang tidak perlu jika dan serupa.







Di awal tahun ini, saya pindah ke Flow. TypeScript juga digunakan di sini, tetapi saya tidak mengenalinya sedikit. Mengapa? Dia terlalu baik kepada saya, seperempat kesalahan klien terkait dengan null dan undefined. Saya mulai mencari tahu apa masalahnya dan menemukan satu baris dalam konfigurasi yang mengubah semua perilaku TypeScript.







Ini adalah penyertaan ketat. Tidak ada, tetapi perlu dihidupkan untuk meningkatkan verifikasi.



TypeScript: ketat



Apa yang ketat? Terdiri dari apa?







Ini adalah kumpulan flag yang dapat diaktifkan secara individual, tetapi menurut saya semuanya sangat berguna. noImplicitAny - sebelum mengaktifkan flag ini, kita dapat mendeklarasikan, misalnya, fungsi yang parameternya akan tersirat, seperti any. Jika kita mengaktifkan flag ini, maka kita harus menambahkan pengetikan di tempat di mana TypeScript tidak dapat menghitung jenis dari konteksnya.



Artinya, dalam kasus kedua, kita harus menambahkan pengetikan, karena tidak ada konteks seperti itu. Dalam kasus ketiga, di mana kita memiliki peta, kita tidak dapat menambahkan pengetikan untuk a, karena jelas dari konteksnya akan ada tipe angka.







noImplicitThis. TypeScript mewajibkan kita untuk mengetik ini jika tidak ada konteks. Jika konteksnya adalah, yaitu objek atau kelas, kita tidak perlu melakukan ini.







selalu Ketat. Menambahkan "gunakan ketat" untuk setiap file. Tapi itu mempengaruhi bagaimana JavaScript menjalankan kode kita. (...)







strictBindCallApply. Untuk beberapa alasan, sebelum mengaktifkan opsi ini, TypeScript tidak mencentang bind, apply dan call for types. Setelah menyalakannya, dia memeriksanya dan tidak mengizinkan kami melakukan hal-hal buruk seperti itu.







tightNullChecks, menurut saya, adalah pemeriksaan yang paling dibutuhkan. Ini mewajibkan kita untuk menunjukkan dalam pengetikan tempat-tempat di mana null atau undefined bisa datang. Sebelum penyertaan, kita bisa meneruskan null atau undefined yang tidak ditentukan secara eksplisit, dan karenanya, mendapatkan error. Setelah itu, kontrolnya akan jauh lebih baik.







Berikutnya, strictFunctionTypes. Situasi di sini sedikit lebih rumit. Bayangkan kita memiliki tiga fungsi. Satu bekerja dengan hewan, satu lagi dengan anjing, dan satu lagi dengan kucing. Seekor anjing dan kucing adalah binatang. Artinya, salah bekerja dengan anjing dengan cara yang sama seperti dengan kucing, karena mereka berbeda. Ini akan bekerja dengan baik pada anjing seperti halnya dengan hewan.



Pilihan ketiga adalah ketika kita mencoba bekerja dengan hewan seperti anjing. Untuk beberapa alasan, ini pada awalnya diizinkan dalam TypeScript, tetapi jika Anda mengaktifkan opsi ini, ini akan menjadi tidak valid dan pemeriksaan tertentu akan dilakukan.







Selanjutnya, strictPropertyInitialization. Ini untuk kelas. Ini mewajibkan kita untuk menetapkan nilai awal baik saat mendeklarasikan properti atau dalam konstruktor. Terkadang ada kalanya Anda perlu menyiasati aturan ini. Anda bisa menggunakan tanda seru, tapi, sekali lagi, ini mengharuskan kami untuk lebih berhati-hati.



Jadi, saya menemukan bahwa kita perlu mengaktifkan ketat. Saya mencoba untuk menyalakannya, dan banyak kesalahan muncul. Oleh karena itu, diputuskan untuk menggunakan konfigurasi transisi menjadi ketat. Kami menetapkan ketat dalam tiga langkah.







Tahap pertama: kami menambahkan "strict": true ke tsconfig, dan, karenanya, lingkungan pengembangan kami meminta kami untuk tempat-tempat dengan kesalahan, yang disebabkan oleh penyertaan strict.



Tetapi untuk webpack, kami membuat tsconfig khusus, yang akan menjadi false, dan menggunakannya saat membuat. Artinya, tidak ada yang rusak selama perakitan, tetapi di editor kami, kami melihat kesalahan ini. Dan kami dapat segera memperbaikinya. Kemudian kami beralih dari waktu ke waktu ke tahap kedua, ini adalah perbaikan. Kami membangun proyek kami dengan tsconfig biasa. Kami memperbaiki beberapa kesalahan yang muncul, dan mengulangi semua ini di waktu luang kami.



Dengan tindakan seperti itu, sejauh ini kami telah mengurangi jumlah kesalahan kami dari 400 menjadi 200. Kami berharap dapat melanjutkan ke tahap ketiga - menghapus webpackTsConfig dan menggunakan tsconfig saat membuat, tetapi dengan pengaktifan yang ketat.



TypeScript:



Anda dapat berbicara sedikit tentang seluk-beluk kecil TypeScript yang tidak tercakup oleh ketat, tetapi sulit untuk diformalkan dengan benar.







Mari kita mulai dengan operator tanda seru. Apa yang memungkinkan Anda lakukan? Dalam kasus ini, rujuk ke bidang yang tidak dapat ditentukan, seolah-olah tidak dapat ditentukan. Masuk akal dalam mode ketat, ketika kami mencoba mengakses bidang, secara eksplisit mengatakan: Saya yakin itu jelas bukan nol atau tidak ditentukan. Tapi ini buruk, karena jika tiba-tiba berubah menjadi null atau tidak terdefinisi, maka secara alami kita mendapatkan error saat runtime.



ESLint akan membantu kita menghindari hal-hal seperti itu, itu hanya akan melarang kita. Kita berhasil. Bagaimana cara memperbaiki contoh sebelumnya sekarang?



Misalkan kita mengalami situasi seperti itu.







Ada elemen, bisa berupa link atau span. Pada dasarnya kita memahami bahwa span hanyalah teks, dan link adalah teks dan link.



(gambar)



Tapi kami lupa memberitahu bahasa TypeScript, jadi dalam fungsi getItemHtml situasi muncul di mana dalam kasus tautan kami harus mengatakan: href bukanlah opsional, itu pasti akan. Ini juga merupakan tempat potensial terjadinya kesalahan. Bagaimana memperbaikinya?







Opsi pertama adalah mengoreksi pengetikan, yaitu, untuk secara eksplisit menunjukkan ke TypeScript bahwa sebuah href diperlukan untuk tautan, dan opsional untuk span.







Dan tanda seru tidak diperlukan di sini.







Opsi koreksi kedua. Misalkan tipe Item tidak dijelaskan oleh kami dan kami tidak bisa begitu saja mengambil dan membatasinya. Lalu kita bisa menulis ulang dengan cara yang sama.







Harap diperhatikan: cek baru saja muncul. Berikutnya adalah pencatatan bahwa programmer tidak mengharapkan nilai ini saat menulis kode ini, jadi di masa mendatang kita akan melihat kesalahan ini dan mengambil tindakan yang sesuai.



Selanjutnya, kami mencoba untuk merender Item kami. Di sini Anda cukup memberikan kesalahan kepada pengguna. Tetapi jika ini adalah beberapa data yang tidak signifikan, maka Anda dapat membuat stub, seperti di sini.



sebagai





Lebih lanjut. Ada juga yang sebagai operator. Apa yang memungkinkan Anda lakukan?







Ini memungkinkan Anda untuk mengatakan - Saya tahu lebih baik, ada jenis ini dan itu - dan juga menuntun diri Anda pada kesalahan.



Array



Metode perjuangannya sama. Yang perlu Anda lebih berhati-hati adalah array. TypeScript bukanlah obat mujarab, ia tidak akan memeriksa beberapa poin. Misalnya, kita bisa merujuk ke elemen array yang tidak ada. Dalam hal ini, kami akan mengambil elemen pertama dari array dan mendapatkan kesalahan dalam kode ini. Bagaimana kita memperbaikinya?







Sekali lagi, ada dua cara. Cara pertama adalah mengetik. Kami mengatakan bahwa kami memiliki elemen pertama, dan tanpa rasa takut mengacu pada elemen ini. Atau kami akan memeriksa, kami akan mencatat, jika ada sesuatu yang tiba-tiba salah, jika kami secara eksplisit mengharapkan array yang tidak kosong.



Objek



Ini sama dengan objek. Kita bisa mendeklarasikan objek yang bisa memiliki sejumlah properti dan juga mendapatkan kesalahan yang tidak ditentukan.







Sekali lagi, Anda dapat membuat indikasi eksplisit tentang properti apa yang diperlukan, atau cukup periksa.



apa saja



Sekarang yang jelas adalah apapun.







Ini memungkinkan Anda untuk merujuk ke properti apa pun dari suatu objek seolah-olah tidak ada pengetikan sama sekali. Dalam hal ini, kita dapat melakukan apapun yang kita inginkan dengan x. Dan sekali lagi tembak kaki Anda sendiri, lakukan kesalahan.



Sekali lagi, lebih baik untuk melarang ini secara eksplisit dengan ESLint. Tetapi ada situasi ketika itu muncul dengan sendirinya.







Misalnya, dalam kasus ini JSON.parse hanya menghasilkan jenis ini saja. Apa yang bisa dilakukan?







Anda cukup mengatakan: Saya tidak mempercayai Anda, lebih baik katakan bahwa saya tidak tahu apa itu, dan saya akan menerimanya. Bagaimana cara menghadapinya? Berikut adalah contoh hipotetisnya.







Ada pengguna, pengguna memiliki nama yang diperlukan dan email opsional.







Kami sedang menulis fungsi parseUser. Dibutuhkan string JSON dan mengembalikan objek kita kepada kita. Sekarang kita mulai memeriksa semua ini. Pertama, kita melihat baris dengan parse dan unknown familiar bagi kita dari slide sebelumnya. Selanjutnya, kami mulai memeriksa.







Jika itu bukan sebuah objek atau null, lemparkan kesalahan.







Lebih lanjut, jika tidak ada properti nama yang diperlukan atau itu bukan string, kami membuat kesalahan. Berikut kode lanjutannya.







Kami mulai membentuk Pengguna, karena semua bidang wajib sudah dikumpulkan.







Selanjutnya kita periksa apakah ada field email. Jika ya, maka kami memeriksa tipenya dan, jika tipenya tidak cocok, kami membuat kesalahan. Jika tidak ada email, maka kami tidak mengirimkan apapun dan mengembalikan hasilnya. Semuanya baik-baik saja. Tetapi Anda perlu menulis banyak untuk tipe yang paling sederhana.







Dan itu membutuhkan banyak pemeriksaan



Kami membutuhkan banyak validasi karena permintaan JSON yang khas terlihat seperti ini.







Tanpa basa-basi lagi, ini hanya fetch dan json (). Konversi dari sembarang ke SomeRequestResponse muncul sebagai gantinya. Ini juga perlu diperjuangkan. Anda dapat melakukannya dengan cara sebelumnya, atau Anda dapat melakukannya dengan sedikit berbeda.



io-ts



Itu sama di bawah tenda: kami menggunakan perpustakaan khusus untuk pemeriksaan tipe. Dalam hal ini, io-ts. Berikut adalah contoh sederhana tentang cara mengatasinya.







Mari kita ambil tipe pengguna sebelumnya dan tulis di dalam perpustakaan yang kita gunakan. Ya, mengetik sedikit lebih rumit di sini, tetapi dua syarat harus dipenuhi secara bersamaan. Ini harus berupa objek dengan bidang nama yang diperlukan dan objek dengan bidang email opsional. Bagaimana kita bisa memeriksa semua ini?







Mari tulis parseUser yang sama. Dalam kasus ini, kami menggunakan metode User.decode. Kami meneruskan objek yang sudah dipasangkan di sana, itu mengembalikan hasilnya kepada kami. Mungkin dalam format yang tidak biasa. Objek bertipe Either, bisa dalam dua status. Yang pertama benar. Ini biasanya berarti semuanya berjalan dengan baik. kiri mengatakan itu tidak berjalan dengan baik. Kedua kondisi ini memiliki properti yang memungkinkan kita mempelajari lebih lanjut. Jika berhasil, ini adalah hasil dari eksekusi, jika terjadi kesalahan, kesalahan.



Kami memeriksa apakah hasil kami di negara bagian kiri. Jika ya, kami katakan bahwa telah terjadi kesalahan. Kemudian, jika semuanya baik-baik saja, kita cukup mengembalikan hasilnya.



Menampilkan kesalahan







Tentang menampilkan kesalahan. Anda bisa sedikit meningkatkannya. Kami akan menggunakan reporter io-ts untuk ini. Ini adalah perpustakaan yang ditulis oleh penulis yang sama dengan io-ts. Ini memungkinkan kesalahan disajikan dengan indah. Apa yang dia lakukan? Kami mengubah kode di sini tempat bebek itu berada. Ini mengambil hasil dan mengembalikan array string. Kami hanya menggabungkannya dalam satu baris dan menampilkannya. Apa yang kita dapatkan pada akhirnya?







Misalkan kita mengirimkan null ke string JSON.







Ini akan memberikan dua kesalahan. Hal ini dikarenakan kehalusan pelaksanaannya, karena kita melakukan intersection. Kesalahannya cukup jelas. Keduanya mengatakan bahwa kami mengharapkan objek tetapi mendapatkan nol. Hanya saja untuk masing-masing kondisi tersebut akan memberikan error tersendiri.







Selanjutnya, mari coba lewati array kosong di sana. Itu akan sama.







Dia hanya akan memberi tahu kita: Saya juga mengharapkan objek, tetapi menerima array kosong.







Jadi, kami terus melihat apa yang akan terjadi jika kami mulai mengirimkan data yang salah. Misalnya, mari berikan objek kosong.







Sekarang ini akan memberikan satu kesalahan tentang fakta bahwa kami tidak memiliki bidang nama yang diperlukan. Dia mengharapkan kolom nama bertipe string, tetapi berakhir dengan undefined. Dari kesalahan ini juga mudah untuk memahami apa yang terjadi.







Selanjutnya, kami akan mencoba melewatkan jenis yang salah di sana. Kami juga mendapatkan kesalahan, hampir sama seperti pada contoh sebelumnya.







Tapi di sini dia dengan jelas menulis kepada kami makna yang kami sampaikan.







Apa lagi yang bisa dilakukan io-ts? Ini memungkinkan Anda untuk mendapatkan tipe TypeScript. Artinya, kami menambahkan baris ini. Dengan hanya menambahkan typeof, juga typeof, kita mendapatkan jenis TypeScript yang selanjutnya dapat kita gunakan dalam aplikasi. Nyaman.







Apa lagi yang bisa dilakukan perpustakaan ini? Mengonversi jenis. Katakanlah kita membuat permintaan ke server. Server mengirimkan tanggal dalam format waktu unix. Dan ada perpustakaan khusus, lagi-lagi dari pencipta perpustakaan io-ts: io-ts-types. Ada transformasi yang awalnya ditulis, dan alat untuk membuat transformasi tersebut lebih mudah ditulis. Kami menambahkan bidang tanggal: itu berasal dari server sebagai nomor, dan kami akhirnya menerimanya sebagai objek Tanggal.



Mari kita gambarkan tipenya



Mari kita lihat apa yang ada di dalam pustaka ini dan coba gambarkan tipe yang paling sederhana.







Pertama, mari kita lihat bagaimana ini dijelaskan secara umum. Ini dijelaskan dengan cara yang sama, agak rumit, mengingat bahwa itu juga diperlukan untuk transformasi. Selain dari server ke klien, jika kita mempertimbangkan interaksi dengan server, dan transformasi sebaliknya, dari klien ke server.



Mari kita sederhanakan tugas kita sedikit. Kami hanya akan menulis jenis yang memeriksa. Dalam kasus ini, mari kita cari tahu apa arti bidang-bidang ini. nama - nama jenis.







Itu diperlukan untuk menampilkan kesalahan. Seperti yang kita lihat pada contoh sebelumnya, kesalahan entah bagaimana mengeja nama tipe. Anda dapat menentukannya di sini.



Selanjutnya, ada fungsi validasi. Dibutuhkan - katakanlah, dari server - nilai tidak diketahui; mengambil konteks untuk menampilkan kesalahan dengan benar; dan mengembalikan objek Either dalam dua status - bisa berupa kesalahan atau nilai yang divalidasi.



Ada dua fungsi lagi: is dan encode. Mereka digunakan untuk mengubah mereka secara terbalik, tapi jangan menyentuhnya untuk saat ini.







Bagaimana tipe string yang paling sederhana dapat direpresentasikan? Kami menetapkan nama menjadi string dan memeriksa apakah itu string. Dengan konversi langsung, ini tidak perlu, tetapi secara resmi kami menulisnya. Dan kemudian kami hanya melakukan jenis untuk memeriksa. Jika berhasil, kita mengembalikan hasil sukses, dan sebagai akibat dari kegagalan. Konteks juga ditambahkan sehingga kesalahan ditampilkan dengan benar. Dan kami hanya mengembalikan hal yang sama, karena tidak ada transformasi terbalik.



Saat latihan



Apa dalam prakteknya? Mengapa kami memutuskan untuk memeriksa data yang berasal dari server?







Minimal, ada JSON dalam database. Kami, tentu saja, percaya bahwa dia akan berlari dengan baik dan dia akan diperiksa di beberapa poin. Tetapi formatnya mungkin sedikit berubah, kita tidak boleh merusak frontend atau segera mencari tahu tentang kesalahan untuk mengambil tindakan pembalasan.



Kami memiliki Python di server tanpa pengetikan eksplisit. Dengan ini juga, terkadang ada masalah kecil. Dan agar tidak rusak, kita cukup mengecek dan juga mengamankan diri kita sendiri, untuk berjaga-jaga.



Tidak ada dokumentasi yang jelas tentang respons server. Mungkin, server lebih khawatir tentang apa yang akan datang kepadanya daripada tentang apa yang akan dia berikan. Ya, ini lebih merupakan masalah kita - bukan untuk merusak.







Apa yang kami temukan? Kami sudah mulai menggunakannya sedikit. Menemukan bahwa server memberi kita objek kosong, bukan array kosong. Saya baru saja melihat-lihat kode - ditulis untuk mengembalikan objek kosong.



Selanjutnya - tidak adanya beberapa bidang. Kami pikir itu wajib, tapi ternyata opsional.



Bidang nullable hilang begitu saja dalam beberapa kasus. Artinya, bidang opsional dapat disajikan dalam dua cara: baik ketika kita tidak meneruskannya, atau ketika kita mengirimkan null. Itu juga tidak selalu datang kepada kami dengan benar. Agar tidak menemukan kesalahan di tengah kode kita, kita dapat menangkapnya hanya berdasarkan permintaan.







Apa yang kita miliki sekarang? Kami telah memeriksa banyak tanggapan dari server dan masuk jika kami tidak menyukai sesuatu. Kemudian kami menganalisis ini dan menetapkan tugas: baik untuk mengubah pengetikan di frontend kami, atau mengedit di backend. Sekarang kami tidak mengubah data yang berasal dari server: jika null sebagai pengganti string, kami tidak mengubahnya, misalnya, menjadi string kosong.



Rencana kami adalah memeriksa dan mencatat, tetapi mengoreksi jika ada kesalahan. Jika kami menerima data yang salah, kami akan memperbaiki nilai ini sehingga pengguna dapat menampilkan setidaknya sesuatu alih-alih jatuh di dalam kode kami.







Hasil kecil. Kami mengaktifkan ketat sehingga TypeScript akan lebih membantu kami, mengecualikan sebagai, apa saja, dan tanda seru. Kami akan lebih berhati-hati dengan array dan objek di TypeScript, dan juga memeriksa semua data eksternal. Omong-omong, ini bukan hanya server. Anda juga dapat memeriksa localStorage, pesan yang datang di acara. Misalnya postMessage.



Terima kasih atas perhatiannya.



All Articles