目錄
型別守衛(type guard)是什麼
型別守衛是TypeScript中的一種技巧,用於設計時期(design time)區分變數的型別,以避免在runtime出錯
像是常見的typeof、in、instanceof都算是簡單的型別守衛
使用type guard的優點是提升type safety,缺點則是可能讓程式碼變長、難讀
你可能會想問為什麼不直接暴力用as就好,因為那會降低整個code base的type safety
const foo = (data) => {
    if('name' in data){
        return `Hello, ${data.name}`;
    }else{
        return data;
    }
}
型別守衛中還有另一種叫做型別預測(type predicate)
型別預測(type predicate)
型別預測是TypeScript 中的一種技巧,用於在runtime區分變數的型別
具體的做法是定義一個函式,這個函式會返回一個布林值,表示這個變數是否符合指定的型別
但是需要注意的是雖然說最後返回的是布林值,但回傳值的型別定義絕對不是boolean,而是要使用Typescript的is進行標記
enum Fruit {
  Apple = 'Apple',
  Banana = 'Banana'
}
// good
const isFruitKey = (f:string):f is keyof typeof Fruit => {
  return f in Fruit;
}
// bad
// 這樣的話即使回傳true,型別也不會被判斷為Fruit內的key
const isFruitKey2 = (f:string):boolean => {
  return f in Fruit;
}
用起來會像這樣

泛型 & 型別預測
上段的例子只能判斷某個字串是否是enum Fruit的key
但如果要讓型別預測的函示更泛用就會需要配合泛型使用
// 因為這個方法會用在物件上,所以用extends object限縮T的型別為物件
const isKeyOfObject = <T extends object>(enumObject: T) => {
    // 因為value是string,所以用Extract限縮value的型別為T的key中是string的那幾個
    return (value: string): value is Extract<keyof T, string> => {
        return value in enumObject;
    }
}
console.log(isKeyOfObject(Fruit)('Apple')); // true
console.log(isKeyOfObject(Fruit)('peach')); // false
判斷某個字串是不是某個enum的value則如下
// 使用Record<string, unknown>而不是object來限縮型別,
// 因為object的話TS會斷言其key可能為string | number | symbol,
// 但是keys為string[],所以一定要限縮T為key為string的物件
const isElementOfObject = <T extends Record<string, unknown>>(enumObject: T) => {
  return (value: unknown):value is T[keyof T] => {
    const keys = Object.keys(enumObject);
    for(let i = 0; i < keys.length - 1; i++){
      if(enumObject[keys[i]] === value){
        return true;
      }
    }
    return false;
  }
};
console.log(isElementOfObject(Fruit)('apple')); // true
console.log(isElementOfObject(Fruit)('peach')); // false
型別斷言函式(assertion function) & 型別預測
斷言函式近似於型別預測,不過在型別不如預期時它會拋出錯誤
// 斷言
const stringAsserting = (value: unknown): asserts value is string => {
  if (typeof value !== "string") throw new Error("Not a string");
}
// 預測
const stringGuard = (value: unknown):  value is string => {
  return typeof value === "string";
}
參考資料
What are Type Predicates in Typescript?
type guard function doesn't work. type still be judged as string
Assertion functions in TypeScript


