TS中特殊型別-any、unknown、never和extends繼承約束、keyof的使用

我用python写Bug發表於2024-08-20

一、any

any型別是沒有任何限制的,一旦變數設定為any等於是把型別檢查關閉了,TS不會去進行校驗,
個人認為既然使用了TS,儘可能還是不要使用any,除非是為了把js專案快速過渡到TS專案,把複雜的型別先用any定義,讓專案能夠快速啟動,但是建議後續還是需要把any重寫成對應的型別

二、unknown

unknown型別是TypeScript 3.0引入的,被稱作安全的any。

unknown型別是安全的,雖然任何值都可以賦給unknown,
但是我們在使用unknown時如果沒有進行型別斷言或基於控制流的型別細化時unknown不可以賦值給其它型別(除了unknown和any外)
同理,在unknown沒有被斷言或細化到一個確切型別之前,是不允許在其上進行任何操作的。

/* 可以把任何值賦值給unknown,但在使用時需要斷言確定型別:as、typeof 等等 */

let anyName: any = "我是任何呀";

let unknownName: unknown = "我不知道呀";

let myName: string;
myName = anyName; // any賦值給其他型別,可以正常編譯
// myName = unknownName; // unknown在沒有斷言前,賦值給其他型別,編譯報錯
myName = unknownName as string; // unknown在這裡斷言為string,可以賦值給string,正常編譯


let unknownNum: unknown;
unknownNum = "123"; // 沒有使用前,定義為string,正常編譯
unknownNum = 456; // 沒有使用前,定義為number,正常編譯

let myNum: number;
// myNum = unknownNum + 20; // 編譯報錯,因為unknown沒有斷言,TS不知道這是什麼型別
myNum = (unknownNum as number) + 20; // 編譯正常,unknown斷言為number,可以進行加法



// 即使是明確定義了一個物件,但是型別為unknown時,在沒有斷言前還是不能使用物件的方法或屬性
let obj: unknown = { test: '測試屬性' };
// console.log(obj.test); // 報錯,因為unknown沒有斷言,TS不知道這是什麼型別,不允許操作

// 定義一個型別
interface MyObject {
  test: string;
}

function printUnknown(unknownObj) {
  unknownObj as MyObject
  console.log(unknownObj.test);
}
printUnknown(obj);

三、never

‌在‌TypeScript中,‌never型別表示那些永不存在的值的型別。它通常用於表示不可到達的程式碼分支或丟擲異常的函式。‌

never型別表示那些永遠不會發生的型別

例如

當一個函式總是丟擲異常或進入無限迴圈: while(true) {}

或者總是會丟擲異常: function foo() { throw new Error('Not Implemented') }返回型別就是never.

never型別的應用場景:

  • 主要用來進行編譯時的全面的檢查,例如你函式里面的 if else if else 分支,是否已經窮盡了所有可能
  • 進行型別校驗
/* 只有never型別本身可以賦值給never型別 */

type reqType = "get" | "post";

function req(method: reqType) {
  if (method === "get") {
    console.log("GET 請求");
  } else if (method === "post") {
    console.log("POST 請求");
  } else {
    // 這裡never代表我們已經把所有分支情況都處理了,如果reqType還有一個型別是 'put' 那麼這就會報錯
    const rejectMethod: never = method;
  }
}
/* 校驗引數型別 */

function countNum<T>(n: T extends number ? T : never) {
  return n;
}
// 這裡可以判斷入參是否為number,如果不是,那麼T就是never,賦值給never就會報錯
countNum(1); // 編譯成功
countNum("a"); // 編譯報錯

四、extends關鍵字既可以用作類繼承,也可以用作泛型約束。

類繼承中,extends用於表示類之間的繼承關係:

class Animal {
  jiao(hour: number) {
    console.log(`Animal叫了這麼久${hour}h.`);
  }
}
 
class Dog extends Animal {
  fei() {
    console.log('wangwang!');
  }
}

泛型約束中,extends用於為泛型變數指定型別約束:

function howLong<T extends { length: number }>(arg: T) {
  console.log(arg.length);
}
howLong<string>('hello'); // 5
/* 這裡T被約束為具有length屬性的型別,這意味著傳給howLong的引數arg必須有一個length屬性,且其型別為數字。
這裡extends更像是一個判斷條件,用於確保泛型引數型別符合特定的約束。*/

五、keyof

keyof TT 型別的鍵集

/* keyof T 是 T 型別的鍵集 */

interface Home {
  addr: string;
  height: string;
}

type h = keyof Home; // 這裡 h 就等於 "addr" | "height"


/* 使用 keyof 進行對映型別,需要注意的是對映型別只能在型別別名(type)中使用,不能在介面中使用 */
interface K {
  a: number;
  b: number;
}
type V1 = { a: number; b: number }

// 上面的寫法可以用keyof簡化
type V2 = { [key in keyof K]: number }

相關文章