Pengetikan Statis Dinamis Dalam TypeScript
Diterbitkan: 2022-03-10JavaScript 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:
- 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 parameteruserID
yang berisi ID pengguna! -
response
atau objekreply
.
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?
- 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. - Bagus bahwa kami secara eksplisit mengirim kode status!
20
bukanlah kode status yang valid. Klien mungkin tidak mengerti apa yang terjadi di sini. - Ini adalah tanggapan yang ingin kami kirimkan kembali. Kami mengakses argumen yang diuraikan tetapi memiliki kesalahan ketik yang berarti. Ini
userID
bukanuserId
. 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.
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 bertipeServerRequest
-
reply
yang bertipeServerReply
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:
-
ServerRequest
menjadi tipe generik, seperti yang ditunjukkan oleh kurung sudut - Kami mendefinisikan parameter generik yang disebut
Met
, yang merupakan subset dari tipeMethods
- 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:
- 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. - 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.
- 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! - 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:
- 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 denganRest
. Misalnya, jika kita meneruskan rute"/api/users/:userID/orders/:orderID"
keParseRouteParams
, kita menyimpulkan"userID"
keP
, dan"orders/:orderID"
keRest
. Kami menyebut tipe yang sama denganRest
- 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. - 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:
- Kami hanya dapat meneruskan kode status numerik yang tepat ke
res.status()
-
req.method
adalah salah satu dari empat kemungkinan string, dan ketika kita menggunakanapp.get
, kita tahu itu hanya"GET"
- 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.
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.