TypeScript Kekuasaan tidak pernah

Ketika saya pertama kali melihat kata tidak pernah, saya berpikir betapa tidak berguna tipe muncul di TypeScript. Seiring waktu, tenggelam lebih dalam ke dalam, aku mulai mengerti betapa kuatnya kata ini. Dan kekuatan ini lahir dari kasing dunia nyata yang ingin saya bagikan dengan pembaca. Siapa peduli, selamat datang ke kucing.

Apa yang tidak pernah


Jika kita terjun ke dalam sejarah, kita akan melihat bahwa tipe itu tidak pernah muncul pada awal TypeScript versi 2.0, dengan deskripsi yang agak sederhana tentang tujuannya. Jika Anda secara singkat dan bebas menceritakan kembali versi pengembang ts, maka tipe tersebut tidak pernah merupakan tipe primitif yang mewakili tanda untuk nilai yang tidak akan pernah terjadi. Atau, tanda untuk fungsi yang tidak akan pernah mengembalikan nilai, baik karena loop-nya, misalnya, loop tak terbatas, atau karena gangguannya. Dan untuk menunjukkan dengan jelas esensi dari apa yang dikatakan, saya mengusulkan untuk melihat contoh di bawah ini:

/**    */ function error(message: string): never { throw new Error(message); } /**   */ function infiniteLoop(): never { while (true) { } } /**   */ function infiniteRec(): never { return infiniteRec(); } 

Mungkin karena contoh-contoh seperti itu, saya mendapat kesan pertama bahwa jenis ini diperlukan untuk kejelasan.

Jenis sistem


Sekarang saya bisa mengatakan bahwa fauna sistem tipe kaya di TypeScript juga tidak pernah jatuh tempo. Dan untuk mendukung kata-kata saya, saya akan memberikan beberapa jenis perpustakaan dari lib.es5.d.ts

 /** Exclude from T those types that are assignable to U */ type Exclude<T, U> = T extends U ? never : T; /** Extract from T those types that are assignable to U */ type Extract<T, U> = T extends U ? T : never; /** Construct a type with the properties of T except for those in type K. */ type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>; /** Exclude null and undefined from T */ type NonNullable<T> = T extends null | undefined ? never : T; /** Obtain the parameters of a function type in a tuple */ type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never; 

Dari tipe saya dengan tidak pernah, saya akan memberikan favorit saya - GetNames, analog yang ditingkatkan dari keyof:

 /** * GetNames      * @template FromType  -   * @template KeepType   * @template Include       .   false -    KeepType */ type GetNames<FromType, KeepType = any, Include = true> = { [K in keyof FromType]: FromType[K] extends KeepType ? Include extends true ? K : never : Include extends true ? never : K }[keyof FromType]; //   class SomeClass { firstName: string; lastName: string; age: number; count: number; getData(): string { return "dummy"; } } // be: "firstName" | "lastName" type StringKeys = GetNames<SomeClass, string>; // be: "age" | "count" type NumberKeys = GetNames<SomeClass, number>; // be: "getData" type FunctionKeys = GetNames<SomeClass, Function>; // be: "firstName" | "lastName" | "age" | "count" type NonFunctionKeys = GetNames<SomeClass, Function, false>; 

Memantau perubahan di masa depan


Ketika kehidupan proyek tidak cepat berlalu, pengembangannya mengambil karakter khusus. Saya ingin memiliki kendali atas poin-poin penting dalam kode, untuk memiliki jaminan bahwa Anda atau anggota tim Anda tidak akan lupa untuk memperbaiki bagian penting dari kode ketika dibutuhkan waktu. Bagian kode semacam itu sangat nyata ketika ada keadaan atau penghitungan sesuatu. Misalnya, daftar serangkaian tindakan pada suatu entitas. Saya sarankan melihat contoh untuk memahami konteks apa yang terjadi.

 //    -   ,     ,   ActionEngine type AdminAction = "CREATE" | "ACTIVATE"; // ,     ,   AdminAction   . class ActionEngine { doAction(action: AdminAction) { switch (action) { case "CREATE": //   return "CREATED"; case "ACTIVATE": //   return "ACTIVATED"; default: throw new Error("   "); } } } 

Kode di atas disederhanakan sebanyak mungkin hanya untuk fokus pada poin penting - jenis AdminAction didefinisikan dalam proyek lain dan bahkan mungkin tidak disertai oleh tim Anda. Karena proyek akan hidup untuk waktu yang lama, maka perlu untuk melindungi ActionEngine Anda dari perubahan dalam tipe AdminAction tanpa sepengetahuan Anda. TypeScript menawarkan beberapa resep untuk mengatasi masalah ini, salah satunya adalah dengan menggunakan tipe never. Untuk melakukan ini, kita perlu mendefinisikan NeverError dan menggunakannya dalam metode doAction.

 class NeverError extends Error { //         - ts   constructor(value: never) { super(`Unreachable statement: ${value}`); } } class ActionEngine { doAction(action: AdminAction) { switch (action) { case "CREATE": //   return "CREATED"; case "ACTIVATE": //   return "ACTIVATED"; default: throw new NeverError(action); // ^       switch  . } } } 

Sekarang tambahkan nilai "BLOCK" baru ke AdminAction dan dapatkan kesalahan pada waktu kompilasi: Argumen tipe '"BLOCK"' tidak dapat ditetapkan ke parameter tipe 'never'.ts (2345).

Pada prinsipnya, kami mencapai ini. Perlu disebutkan poin menarik bahwa konstruksi sakelar melindungi kita dari mengubah elemen AdminAction atau menghapusnya dari set. Dari latihan, saya bisa mengatakan itu benar-benar berfungsi seperti yang diharapkan.

Jika Anda tidak ingin memperkenalkan kelas NeverError, Anda dapat mengontrol kode dengan mendeklarasikan variabel tipe tidak pernah. Seperti ini:

 type AdminAction = "CREATE" | "ACTIVATE" | "BLOCK"; class ActionEngine { doAction(action: AdminAction) { switch (action) { case "CREATE": //   return "CREATED"; case "ACTIVATE": //   return "ACTIVATED"; default: const unknownAction: never = action; // Type '"BLOCK"' is not assignable to type 'never'.ts(2322) throw new Error(`   ${unknownAction}`); } } } 

Batas Konteks: ini + tidak pernah


Trik berikut ini sering menyelamatkan saya dari kesalahan konyol di tengah kelelahan atau kecerobohan. Dalam contoh di bawah ini, saya tidak akan memberikan penilaian terhadap kualitas pendekatan yang dipilih. Bersama kami, de facto, ini terjadi. Misalkan Anda menggunakan metode di kelas yang tidak memiliki akses ke bidang kelas. Ya, kedengarannya menakutkan - semua ini adalah gov ... kode.

 @SomeDecorator({...}) class SomeUiPanel { @Inject private someService: SomeService; public beforeAccessHook() { //    ,    ,     SomeUiPanel this.someService.doInit("Bla bla"); // ^       :  beforeAccessHook   ,      } } 

Dalam kasus yang lebih luas, bisa berupa fungsi callback atau panah, yang memiliki konteks eksekusi sendiri. Dan tugasnya adalah: Bagaimana melindungi diri Anda dari kesalahan runtime? Untuk ini, TypeScript memiliki kemampuan untuk menentukan konteks ini .

 @SomeDecorator({...}) class SomeUiPanel { @Inject private someService: SomeService; public beforeAccessHook(this: never) { //    ,    ,     SomeUiPanel this.someService.doInit("Bla bla"); // ^ Property 'someService' does not exist on type 'never' } } 

Dalam keadilan, saya akan mengatakan bahwa itu tidak pernah pantas. Sebagai gantinya, Anda dapat menggunakan void dan {}. Tetapi ini adalah tipe yang tidak pernah menarik perhatian ketika Anda membaca kode.

Harapan


Invarian


Memiliki gagasan pasti tentang tidak pernah, saya pikir kode berikut ini akan berfungsi:

 type Maybe<T> = T | void; function invariant<Cond extends boolean>(condition: Cond, message: string): Cond extends true ? void : never { if (condition) { return; } throw new Error(message); } function f(x: Maybe<number>, c: number) { if (c > 0) { invariant(typeof x === "number", "When c is positive, x should be number"); (x + 1); // works because x has been refined to "number" } } 

Tapi sayang sekali. Ekspresi (x + 1) melempar kesalahan: Operator '+' tidak dapat diterapkan ke jenis 'Mungkin' dan '1'. Contohnya sendiri saya melihat dalam artikel Mentransfer 30.000 baris kode dari Flow ke TypeScript.

Ikatan fleksibel


Saya berpikir bahwa dengan bantuan tidak pernah saya dapat mengontrol parameter fungsi wajib dan, dalam kondisi tertentu, menonaktifkan yang tidak perlu. Tapi tidak, itu tidak akan berhasil:

 function variants<Type extends number | string>(x: Type, c: Type extends number ? number : never): number { if (typeof x === "number") { return x + c; } return +x; } const three = variants(1, 2); // ok // 2  - never,     string. ,   const one = variants("1"); // expected 2 arguments, but got 1.ts(2554) 

Masalah di atas diselesaikan dengan cara yang berbeda .

Verifikasi lebih ketat


Saya ingin kompiler ts untuk tidak melewatkan sesuatu seperti itu bertentangan dengan akal sehat.

 variants(<never> {}, <never> {}); 

Kesimpulan


Pada akhirnya, saya ingin menawarkan tugas kecil, dari serangkaian keanehan aneh. Baris mana yang salah?

 class never<never> { never: never; } const whats = new never<string>(); whats.never = ""; 

Opsi
Dalam yang terakhir: Ketik '""' tidak dapat ditugaskan untuk mengetik 'tidak pernah' .ts (2322)

Itu saja yang saya ingin katakan tentang tidak pernah. Terima kasih atas perhatian Anda dan sampai jumpa lagi.

Source: https://habr.com/ru/post/id471026/


All Articles