Pengetikan Statis Dinamis Dalam TypeScript

Diterbitkan: 2022-03-10
Ringkasan cepat Dalam artikel ini, kita melihat beberapa fitur TypeScript yang lebih canggih, seperti tipe gabungan, tipe bersyarat, tipe literal templat, dan generik. Kami ingin memformalkan perilaku JavaScript paling dinamis dengan cara yang dapat menangkap sebagian besar bug sebelum terjadi. Kami menerapkan beberapa pembelajaran dari semua bab TypeScript dalam 50 Pelajaran, sebuah buku yang kami terbitkan di sini di Majalah Smashing akhir 2020. Jika Anda tertarik untuk mempelajari lebih lanjut, pastikan untuk memeriksanya!

JavaScript adalah bahasa pemrograman yang dinamis secara inheren. Kami sebagai pengembang dapat mengekspresikan banyak hal dengan sedikit usaha, dan bahasa serta waktu prosesnya mengetahui apa yang ingin kami lakukan. Inilah yang membuat JavaScript begitu populer untuk pemula, dan yang membuat pengembang berpengalaman menjadi produktif! Namun, ada peringatan: Kita harus waspada! Kesalahan, kesalahan ketik, perilaku program yang benar: Banyak hal yang terjadi di kepala kita!

Perhatikan contoh berikut.

 app.get("/api/users/:userID", function(req, res) { if (req.method === "POST") { res.status(20).send({ message: "Got you, user " + req.params.userId }); } })

Kami memiliki server https://expressjs.com/-style yang memungkinkan kami untuk menentukan rute (atau jalur), dan menjalankan panggilan balik jika URL diminta.

Panggilan balik membutuhkan dua argumen:

  1. Objek request .
    Di sini kita mendapatkan informasi tentang metode HTTP yang digunakan (misalnya GET, POST, PUT, DELETE), dan parameter tambahan yang masuk. Dalam contoh ini, userID harus dipetakan ke parameter userID yang berisi ID pengguna!
  2. response atau objek reply .
    Di sini kami ingin menyiapkan respons yang tepat dari server ke klien. Kami ingin mengirim kode status yang benar ( status metode) dan mengirim output JSON melalui kabel.

Apa yang kita lihat dalam contoh ini sangat disederhanakan, tetapi memberikan gambaran yang bagus tentang apa yang kita lakukan. Contoh di atas juga penuh dengan kesalahan! Lihat:

 app.get("/api/users/:userID", function(req, res) { if (req.method === "POST") { /* Error 1 */ res.status(20).send({ /* Error 2 */ message: "Welcome, user " + req.params.userId /* Error 3 */ }); } })

Oh wow! Tiga baris kode implementasi, dan tiga kesalahan? Apa yang telah terjadi?

  1. Kesalahan pertama bernuansa. Sementara kami memberi tahu aplikasi kami bahwa kami ingin mendengarkan permintaan GET (karenanya app.get ), kami hanya melakukan sesuatu jika metode permintaannya adalah POST . Pada titik tertentu dalam aplikasi kita, req.method tidak boleh POST . Jadi kami tidak akan pernah mengirim tanggapan apa pun, yang mungkin menyebabkan waktu tunggu yang tidak terduga.
  2. Bagus bahwa kami secara eksplisit mengirim kode status! 20 bukanlah kode status yang valid. Klien mungkin tidak mengerti apa yang terjadi di sini.
  3. Ini adalah tanggapan yang ingin kami kirimkan kembali. Kami mengakses argumen yang diuraikan tetapi memiliki kesalahan ketik yang berarti. Ini userID bukan userId . Semua pengguna kami akan disambut dengan "Selamat datang, pengguna tidak ditentukan!". Sesuatu yang pasti pernah Anda lihat di alam liar!

Dan hal-hal seperti itu terjadi! Terutama di JavaScript. Kami mendapatkan ekspresif – tidak sekali pun kami harus repot tentang tipe – tetapi harus memperhatikan apa yang kami lakukan.

Di sinilah JavaScript mendapat banyak reaksi dari programmer yang tidak terbiasa dengan bahasa pemrograman dinamis. Mereka biasanya memiliki kompiler yang mengarahkan mereka ke kemungkinan masalah dan menangkap kesalahan di muka. Mereka mungkin terlihat angkuh ketika mereka tidak menyukai jumlah pekerjaan ekstra yang harus Anda lakukan di kepala Anda untuk memastikan semuanya bekerja dengan benar. Mereka bahkan mungkin memberi tahu Anda bahwa JavaScript tidak memiliki tipe. Yang tidak benar.

Anders Hejlsberg, arsitek utama TypeScript, mengatakan dalam keynote MS Build 2017-nya bahwa “ bukannya JavaScript tidak memiliki sistem tipe. Tidak ada cara untuk memformalkannya ”.

Dan ini adalah tujuan utama TypeScript. TypeScript ingin memahami kode JavaScript Anda lebih baik daripada Anda. Dan jika TypeScript tidak dapat memahami apa yang Anda maksud, Anda dapat membantu dengan memberikan informasi jenis tambahan.

Lebih banyak setelah melompat! Lanjutkan membaca di bawah ini

Pengetikan Dasar

Dan inilah yang akan kita lakukan sekarang. Mari kita ambil metode get dari server gaya Express kami dan tambahkan informasi jenis yang cukup sehingga kami dapat mengecualikan sebanyak mungkin kategori kesalahan.

Kita mulai dengan beberapa informasi tipe dasar. Kami memiliki objek app yang menunjuk ke fungsi get . Fungsi get mengambil path , yang merupakan string, dan panggilan balik.

 const app = { get, /* post, put, delete, ... to come! */ }; function get(path: string, callback: CallbackFn) { // to be implemented --> not important right now }

Sementara string adalah tipe dasar yang disebut primitif , CallbackFn adalah tipe gabungan yang harus kita definisikan secara eksplisit.

CallbackFn adalah tipe fungsi yang membutuhkan dua argumen:

  • req , yang bertipe ServerRequest
  • reply yang bertipe ServerReply

CallbackFn mengembalikan void .

 type CallbackFn = (req: ServerRequest, reply: ServerReply) => void;

ServerRequest adalah objek yang cukup kompleks di sebagian besar kerangka kerja. Kami melakukan versi yang disederhanakan untuk tujuan demonstrasi. Kami meneruskan string method , untuk "GET" , "POST" , "PUT" , "DELETE" , dll. Ini juga memiliki catatan params . Record adalah objek yang mengasosiasikan satu set kunci dengan satu set properti. Untuk saat ini, kami ingin mengizinkan setiap kunci string dipetakan ke properti string . Kami refactor yang ini nanti.

 type ServerRequest = { method: string; params: Record<string, string>; };

Untuk ServerReply , kami meletakkan beberapa fungsi, mengetahui bahwa objek ServerReply yang sebenarnya memiliki lebih banyak lagi. Fungsi send mengambil argumen opsional dengan data yang ingin kita kirim. Dan kami memiliki kemungkinan untuk mengatur kode status dengan fungsi status .

 type ServerReply = { send: (obj?: any) => void; status: (statusCode: number) => ServerReply; };

Itu sudah sesuatu, dan kami dapat mengesampingkan beberapa kesalahan:

 app.get("/api/users/:userID", function(req, res) { if(req.method === 2) { // ^^^^^^^^^^^^^^^^^ Error, type number is not assignable to string res.status("200").send() // ^^^^^ Error, type string is not assignable to number } })

Tetapi kami masih dapat mengirim kode status yang salah (nomor apa pun dimungkinkan) dan tidak memiliki petunjuk tentang metode HTTP yang mungkin (string apa pun dimungkinkan). Mari kita perbaiki tipe kita.

Set yang Lebih Kecil

Anda dapat melihat tipe primitif sebagai kumpulan dari semua kemungkinan nilai dari kategori tertentu. Misalnya, string mencakup semua kemungkinan string yang dapat diekspresikan dalam JavaScript, number mencakup semua kemungkinan angka dengan presisi float ganda. boolean mencakup semua nilai boolean yang mungkin, yaitu true dan false .

TypeScript memungkinkan Anda untuk memperbaiki set tersebut menjadi subset yang lebih kecil. Misalnya, kita dapat membuat Method tipe yang menyertakan semua kemungkinan string yang dapat kita terima untuk metode HTTP:

 type Methods= "GET" | "POST" | "PUT" | "DELETE"; type ServerRequest = { method: Methods; params: Record<string, string>; };

Method adalah kumpulan yang lebih kecil dari kumpulan string yang lebih besar. Method adalah tipe gabungan dari tipe literal. Tipe literal adalah unit terkecil dari himpunan yang diberikan. Sebuah string literal. Sebuah angka literal. Tidak ada ambiguitas. Itu hanya "GET" . Anda menggabungkannya dengan tipe literal lainnya, membuat subset dari tipe apa pun yang lebih besar yang Anda miliki. Anda juga dapat melakukan subset dengan tipe literal string dan number , atau tipe objek gabungan yang berbeda. Ada banyak kemungkinan untuk menggabungkan dan memasukkan tipe literal ke dalam serikat pekerja.

Ini memiliki efek langsung pada panggilan balik server kami. Tiba-tiba, kita dapat membedakan antara keempat metode tersebut (atau lebih jika perlu), dan dapat menghabiskan semua kemungkinan dalam kode. TypeScript akan memandu kita:

 app.get("/api/users/:userID", function (req, res) { // at this point, TypeScript knows that req.method // can take one of four possible values switch (req.method) { case "GET": break; case "POST": break; case "DELETE": break; case "PUT": break; default: // here, req.method is never req.method; } });

Dengan setiap pernyataan case yang Anda buat, TypeScript dapat memberi Anda informasi tentang opsi yang tersedia. Cobalah sendiri. Jika Anda kehabisan semua opsi, TypeScript akan memberi tahu Anda di cabang default Anda bahwa ini tidak akan never terjadi. Ini secara harfiah adalah tipe never , yang berarti Anda mungkin telah mencapai status kesalahan yang perlu Anda tangani.

Itu salah satu kategori kesalahan yang lebih sedikit. Kami sekarang tahu persis metode HTTP mana yang mungkin tersedia.

Kita dapat melakukan hal yang sama untuk kode status HTTP, dengan mendefinisikan subset dari nomor valid yang dapat diambil oleh statusCode :

 type StatusCode = 100 | 101 | 102 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 226 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 420 | 422 | 423 | 424 | 425 | 426 | 428 | 429 | 431 | 444 | 449 | 450 | 451 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 598 | 599; type ServerReply = { send: (obj?: any) => void; status: (statusCode: StatusCode) => ServerReply; };

Ketik StatusCode sekali lagi merupakan jenis serikat pekerja. Dan dengan itu, kami mengecualikan kategori kesalahan lain. Tiba-tiba, kode seperti itu gagal:

 app.get("/api/user/:userID", (req, res) => { if(req.method === "POS") { // ^^^^^^^^^^^^^^^^^^^ 'Methods' and '"POS"' have no overlap. res.status(20) // ^^ '20' is not assignable to parameter of type 'StatusCode' } })
Dan perangkat lunak kami menjadi jauh lebih aman! Tapi kita bisa berbuat lebih banyak!

Masukkan Generik

Saat kita mendefinisikan rute dengan app.get , kita secara implisit mengetahui bahwa satu-satunya metode HTTP yang mungkin adalah "GET" . Tetapi dengan definisi tipe kami, kami masih harus memeriksa semua bagian yang mungkin dari serikat pekerja.

Jenis untuk CallbackFn benar, karena kita dapat mendefinisikan fungsi panggilan balik untuk semua metode HTTP yang mungkin, tetapi jika kita secara eksplisit memanggil app.get , alangkah baiknya untuk menyimpan beberapa langkah tambahan yang hanya diperlukan untuk mematuhi pengetikan.

Obat generik TypeScript dapat membantu! Generik adalah salah satu fitur utama dalam TypeScript yang memungkinkan Anda mendapatkan perilaku paling dinamis dari tipe statis. Dalam TypeScript in 50 Lessons, kita menghabiskan tiga bab terakhir untuk menggali semua seluk-beluk obat generik dan fungsionalitas uniknya.

Apa yang perlu Anda ketahui sekarang adalah bahwa kita ingin mendefinisikan ServerRequest dengan cara yang kita dapat menentukan bagian dari Methods bukan seluruh set. Untuk itu, kami menggunakan sintaks generik di mana kami dapat mendefinisikan parameter seperti yang akan kami lakukan dengan fungsi:

 type ServerRequest<Met extends Methods> = { method: Met; params: Record<string, string>; };

Inilah yang terjadi:

  1. ServerRequest menjadi tipe generik, seperti yang ditunjukkan oleh kurung sudut
  2. Kami mendefinisikan parameter generik yang disebut Met , yang merupakan subset dari tipe Methods
  3. Kami menggunakan parameter generik ini sebagai variabel generik untuk mendefinisikan metode.

Saya juga mendorong Anda untuk membaca artikel saya tentang penamaan parameter generik.

Dengan perubahan itu, kita dapat menentukan ServerRequest s yang berbeda tanpa menduplikasi hal-hal:

 type OnlyGET = ServerRequest<"GET">; type OnlyPOST = ServerRequest<"POST">; type POSTorPUT = ServerRquest<"POST" | "PUT">;

Karena kita mengubah antarmuka ServerRequest , kita harus membuat perubahan pada semua tipe lain yang menggunakan ServerRequest , seperti CallbackFn dan fungsi get :

 type CallbackFn<Met extends Methods> = ( req: ServerRequest<Met>, reply: ServerReply ) => void; function get(path: string, callback: CallbackFn<"GET">) { // to be implemented }

Dengan fungsi get , kita meneruskan argumen aktual ke tipe generik kita. Kita tahu bahwa ini bukan hanya subset dari Methods , kita tahu persis subset mana yang kita hadapi.

Sekarang, ketika kami menggunakan app.get , kami hanya memiliki kemungkinan nilai untuk req.method :

 app.get("/api/users/:userID", function (req, res) { req.method; // can only be get });

Ini memastikan bahwa kami tidak berasumsi bahwa metode HTTP seperti "POST" atau yang serupa tersedia saat kami membuat callback app.get . Kita tahu persis apa yang kita hadapi saat ini, jadi mari kita renungkan itu dalam tipe kita.

Kami telah melakukan banyak hal untuk memastikan bahwa request.method diketik secara wajar dan mewakili keadaan sebenarnya. Satu manfaat bagus yang kita dapatkan dengan mensubset tipe serikat Methods adalah bahwa kita dapat membuat fungsi panggilan balik tujuan umum di luar app.get yang aman untuk tipe:

 const handler: CallbackFn<"PUT" | "POST"> = function(res, req) { res.method // can be "POST" or "PUT" }; const handlerForAllMethods: CallbackFn<Methods> = function(res, req) { res.method // can be all methods }; app.get("/api", handler); // ^^^^^^^ Nope, we don't handle "GET" app.get("/api", handlerForAllMethods); // This works

Mengetik Param

Apa yang belum kita sentuh adalah mengetik objek params . Sejauh ini, kami mendapatkan catatan yang memungkinkan mengakses setiap kunci string . Ini tugas kita sekarang untuk membuatnya sedikit lebih spesifik!

Kami melakukannya dengan menambahkan variabel generik lain. Satu untuk metode, satu untuk kemungkinan kunci di Record kami :

 type ServerRequest<Met extends Methods, Par extends string = string> = { method: Met; params: Record<Par, string>; };

Variabel tipe generik Par dapat menjadi subset dari tipe string , dan nilai defaultnya adalah setiap string. Dengan itu, kami dapat memberi tahu ServerRequest kunci mana yang kami harapkan:

 // request.method = "GET" // request.params = { // userID: string // } type WithUserID = ServerRequest<"GET", "userID">

Mari tambahkan argumen baru ke fungsi get dan tipe CallbackFn , sehingga kita dapat mengatur parameter yang diminta:

 function get<Par extends string = string>( path: string, callback: CallbackFn<"GET", Par> ) { // to be implemented } type CallbackFn<Met extends Methods, Par extends string> = ( req: ServerRequest<Met, Par>, reply: ServerReply ) => void;

Jika kita tidak menyetel Par secara eksplisit, jenisnya berfungsi seperti biasanya, karena Par default ke string . Jika kita mengaturnya, kita tiba-tiba memiliki definisi yang tepat untuk objek req.params !

 app.get<"userID">("/api/users/:userID", function (req, res) { req.params.userID; // Works!! req.params.anythingElse; // doesn't work!! });

Itu keren! Ada satu hal kecil yang bisa diperbaiki. Kami masih dapat meneruskan setiap string ke argumen path app.get . Bukankah lebih baik jika kita bisa mencerminkan Par di sana juga?

Kita dapat! Dengan rilis versi 4.1, TypeScript dapat membuat tipe literal templat . Secara sintaksis, mereka bekerja seperti literal templat string, tetapi pada level tipe. Di mana kami dapat membagi set string menjadi subset dengan tipe literal string (seperti yang kami lakukan dengan Metode), tipe literal templat memungkinkan kami untuk menyertakan seluruh spektrum string.

Mari buat tipe yang disebut IncludesRouteParams , di mana kita ingin memastikan bahwa Par disertakan dengan benar dalam cara gaya Express menambahkan titik dua di depan nama parameter:

 type IncludesRouteParams<Par extends string> = | `${string}/:${Par}` | `${string}/:${Par}/${string}`;

Tipe generik IncludesRouteParams mengambil satu argumen, yang merupakan subset dari string . Ini menciptakan tipe gabungan dari dua literal templat:

  1. Literal template pertama dimulai dengan string apa pun , kemudian menyertakan karakter / diikuti oleh karakter : , diikuti dengan nama parameter. Ini memastikan bahwa kami menangkap semua kasus di mana parameter berada di akhir string rute.
  2. Literal templat kedua dimulai dengan string apa pun , diikuti dengan pola yang sama dari / , : dan nama parameter. Kemudian kita memiliki / karakter lain, diikuti oleh string apa pun . Cabang dari tipe gabungan ini memastikan kami menangkap semua kasus di mana parameternya berada di suatu tempat dalam suatu rute.

Beginilah cara IncludesRouteParams dengan nama parameter userID berperilaku dengan kasus pengujian yang berbeda:

 const a: IncludeRouteParams<"userID"> = "/api/user/:userID" // const a: IncludeRouteParams<"userID"> = "/api/user/:userID/orders" // const a: IncludeRouteParams<"userID"> = "/api/user/:userId" // const a: IncludeRouteParams<"userID"> = "/api/user" // const a: IncludeRouteParams<"userID"> = "/api/user/:userIDAndmore" //

Mari sertakan tipe utilitas baru kita dalam deklarasi fungsi get .

 function get<Par extends string = string>( path: IncludesRouteParams<Par>, callback: CallbackFn<"GET", Par> ) { // to be implemented } app.get<"userID">( "/api/users/:userID", function (req, res) { req.params.userID; // YEAH! } );

Besar! Kami mendapatkan mekanisme keamanan lain untuk memastikan bahwa kami tidak ketinggalan menambahkan parameter ke rute yang sebenarnya! Betapa kuatnya.

Binding generik

Tapi coba tebak, saya masih tidak senang dengan itu. Ada beberapa masalah dengan pendekatan itu yang menjadi jelas saat rute Anda menjadi sedikit lebih rumit.

  1. Masalah pertama yang saya miliki adalah bahwa kita perlu secara eksplisit menyatakan parameter kita dalam parameter tipe generik. Kita harus mengikat Par ke "userID" , meskipun kita tetap akan menentukannya dalam argumen jalur fungsi. Ini bukan JavaScript-y!
  2. Pendekatan ini hanya menangani satu parameter rute. Saat kita menambahkan serikat, misalnya "userID" | "orderId" "userID" | "orderId" pemeriksaan failsafe puas dengan hanya satu dari argumen yang tersedia. Begitulah cara kerja set. Itu bisa satu, atau yang lain.

Pasti ada cara yang lebih baik. Dan ada. Jika tidak, artikel ini akan berakhir dengan nada yang sangat pahit.

Mari kita membalik urutannya! Mari kita tidak mencoba mendefinisikan parameter rute dalam variabel tipe generik, melainkan mengekstrak variabel dari path yang kita lewati sebagai argumen pertama app.get .

Untuk mendapatkan nilai sebenarnya, kita harus melihat cara kerja penjilidan generik di TypeScript. Mari kita ambil fungsi identity ini misalnya:

 function identity<T>(inp: T) : T { return inp }

Ini mungkin fungsi generik paling membosankan yang pernah Anda lihat, tetapi ini menggambarkan satu hal dengan sempurna. identity mengambil satu argumen, dan mengembalikan input yang sama lagi. Tipenya adalah tipe generik T , dan juga mengembalikan tipe yang sama.

Sekarang kita dapat mengikat T ke string , misalnya:

 const z = identity<string>("yes"); // z is of type string

Pengikatan generik yang eksplisit ini memastikan bahwa kita hanya meneruskan strings ke identity , dan karena kita mengikat secara eksplisit, tipe yang dikembalikan juga string . Jika kita lupa untuk mengikat, sesuatu yang menarik terjadi:

 const y = identity("yes") // y is of type "yes"

Dalam hal ini, TypeScript menyimpulkan tipe dari argumen yang Anda berikan, dan mengikat T ke tipe literal string "yes" . Ini adalah cara yang bagus untuk mengonversi argumen fungsi menjadi tipe literal, yang kemudian kita gunakan dalam tipe generik lainnya.

Mari kita lakukan itu dengan mengadaptasi app.get .

 function get<Path extends string = string>( path: Path, callback: CallbackFn<"GET", ParseRouteParams<Path>> ) { // to be implemented }

Kami menghapus tipe generik Par dan menambahkan Path . Path dapat berupa subset dari string apa pun . Kami menetapkan path ke tipe generik ini Path , yang berarti saat kami melewati parameter ke get , kami menangkap tipe literal stringnya. Kami meneruskan Path ke ParseRouteParams tipe generik baru yang belum kami buat.

Mari kita bekerja di ParseRouteParams . Di sini, kami mengganti urutan acara lagi. Alih-alih meneruskan parameter rute yang diminta ke generik untuk memastikan jalurnya baik-baik saja, kami melewati jalur rute dan mengekstrak parameter rute yang mungkin. Untuk itu, kita perlu membuat tipe kondisional.

Jenis Bersyarat Dan Jenis Literal Template Rekursif

Tipe kondisional secara sintaksis mirip dengan operator ternary dalam JavaScript. Anda memeriksa suatu kondisi, dan jika kondisi terpenuhi, Anda mengembalikan cabang A, jika tidak, Anda mengembalikan cabang B. Misalnya:

 type ParseRouteParams<Rte> = Rte extends `${string}/:${infer P}` ? P : never;

Di sini, kami memeriksa apakah Rte adalah subset dari setiap jalur yang diakhiri dengan parameter di akhir gaya Express (dengan "/:" sebelumnya). Jika demikian, kami menyimpulkan string ini. Yang berarti kita menangkap isinya menjadi variabel baru. Jika kondisi terpenuhi, kami mengembalikan string yang baru diekstraksi, jika tidak, kami mengembalikan tidak pernah, seperti pada: "Tidak ada parameter rute",

Jika kita mencobanya, kita mendapatkan sesuatu seperti itu:

 type Params = ParseRouteParams<"/api/user/:userID"> // Params is "userID" type NoParams = ParseRouteParams<"/api/user"> // NoParams is never --> no params!

Bagus, itu sudah jauh lebih baik daripada yang kita lakukan sebelumnya. Sekarang, kami ingin menangkap semua parameter lain yang mungkin. Untuk itu, kita harus menambahkan kondisi lain:

 type ParseRouteParams<Rte> = Rte extends `${string}/:${infer P}/${infer Rest}` ? P | ParseRouteParams<`/${Rest}`> : Rte extends `${string}/:${infer P}` ? P : never;

Jenis kondisional kami sekarang berfungsi sebagai berikut:

  1. Pada kondisi pertama, kami memeriksa apakah ada parameter rute di suatu tempat di antara rute. Jika demikian, kami mengekstrak parameter rute dan semua yang muncul setelah itu. Kami mengembalikan parameter rute P yang baru ditemukan dalam gabungan di mana kami memanggil tipe generik yang sama secara rekursif dengan Rest . Misalnya, jika kita meneruskan rute "/api/users/:userID/orders/:orderID" ke ParseRouteParams , kita menyimpulkan "userID" ke P , dan "orders/:orderID" ke Rest . Kami menyebut tipe yang sama dengan Rest
  2. Di sinilah kondisi kedua masuk. Di sini kami memeriksa apakah ada tipe di akhir. Ini adalah kasus untuk "orders/:orderID" . Kami mengekstrak "orderID" dan mengembalikan tipe literal ini.
  3. Jika tidak ada lagi parameter rute yang tersisa, kami tidak akan pernah kembali.

Dan Vanderkam menunjukkan tipe yang serupa, dan lebih rumit untuk ParseRouteParams , tetapi yang Anda lihat di atas juga akan berfungsi. Jika kami mencoba ParseRouteParams kami yang baru diadaptasi, kami mendapatkan sesuatu seperti ini:

 // Params is "userID" type Params = ParseRouteParams<"/api/user/:userID"> // MoreParams is "userID" | "orderID" type MoreParams = ParseRouteParams<"/api/user/:userID/orders/:orderId">

Mari kita terapkan tipe baru ini dan lihat seperti apa penggunaan terakhir app.get .

 app.get("/api/users/:userID/orders/:orderID", function (req, res) { req.params.userID; // YES!! req.params.orderID; // Also YES!!! });

Wow. Itu hanya terlihat seperti kode JavaScript yang kami miliki di awal!

Jenis Statis Untuk Perilaku Dinamis

Jenis yang baru saja kita buat untuk satu fungsi app.get pastikan bahwa kita mengecualikan banyak kemungkinan kesalahan:

  1. Kami hanya dapat meneruskan kode status numerik yang tepat ke res.status()
  2. req.method adalah salah satu dari empat kemungkinan string, dan ketika kita menggunakan app.get , kita tahu itu hanya "GET"
  3. Kami dapat mengurai params rute dan memastikan bahwa kami tidak memiliki kesalahan ketik di dalam panggilan balik kami

Jika kita melihat contoh dari awal artikel ini, kita mendapatkan pesan kesalahan berikut:

 app.get("/api/users/:userID", function(req, res) { if (req.method === "POST") { // ^^^^^^^^^^^^^^^^^^^^^ // This condition will always return 'false' // since the types '"GET"' and '"POST"' have no overlap. res.status(20).send({ // ^^ // Argument of type '20' is not assignable to // parameter of type 'StatusCode' message: "Welcome, user " + req.params.userId // ^^^^^^ // Property 'userId' does not exist on type // '{ userID: string; }'. Did you mean 'userID'? }); } })

Dan semua itu sebelum kita benar-benar menjalankan kode kita! Server gaya ekspres adalah contoh sempurna dari sifat dinamis JavaScript. Bergantung pada metode yang Anda panggil, string yang Anda berikan untuk argumen pertama, banyak perilaku berubah di dalam panggilan balik. Ambil contoh lain dan semua tipe Anda terlihat sangat berbeda.

Tetapi dengan beberapa tipe yang terdefinisi dengan baik, kita dapat menangkap perilaku dinamis ini saat mengedit kode kita. Pada waktu kompilasi dengan tipe statis, bukan pada saat runtime ketika segalanya menjadi booming!

Dan inilah kekuatan TypeScript. Sistem tipe statis yang mencoba memformalkan semua perilaku JavaScript dinamis yang kita semua tahu dengan baik. Jika Anda ingin mencoba contoh yang baru saja kita buat, pergilah ke taman bermain TypeScript dan mainkan dengannya.


TypeScript dalam 50 Pelajaran oleh Stefan Baumgartner Dalam artikel ini, kami menyentuh banyak konsep. Jika Anda ingin tahu lebih banyak, lihat TypeScript di 50 Pelajaran, di mana Anda mendapatkan pengenalan lembut untuk sistem tipe dalam pelajaran kecil yang mudah dicerna. Versi ebook segera tersedia, dan buku cetak akan menjadi referensi yang bagus untuk perpustakaan pengkodean Anda.