[译]<<Effective TypeScript>> 高效TypeScript62个技巧 技巧22

157 阅读2分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第14天,点击查看活动详情

本文的翻译于<<Effective TypeScript>>, 特别感谢!! ps: 本文会用简洁, 易懂的语言描述原书的所有要点. 如果能看懂这文章,将节省许多阅读时间. 如果看不懂,务必给我留言, 我回去修改.

技巧22: 理解类型缩小

类型扩展的反面是类型缩小.

  1. null 检查最常见到类型缩小:

    const el = document.getElementById('foo'); // Type is HTMLElement | null
    if (el) {
      el // Type is HTMLElement
      el.innerHTML = 'Party Time'.blink();
    } else {
      el // Type is null
      alert('No element #foo');
    }
    

    ts 非常擅长在if分支判断缩小类型.尽管有时候可能因为别名受阻(见 技巧 24)

  2. 通过if判断抛出错误或者return, 也能缩小类型范围:

    const el = document.getElementById('foo'); // Type is HTMLElement | null
    if (!el) throw new Error('Unable to find #foo');
    el; // Now type is HTMLElement
    el.innerHTML = 'Party Time'.blink();
    
  3. instanceof:

    function contains(text: string, search: string|RegExp) {
      if (search instanceof RegExp) {
        search  // Type is RegExp
        return !!search.exec(text);
      }
    search  // Type is string
      return text.includes(search);
    }
    
  4. 属性检查:

    interface A { a: number }
    interface B { b: number }
    function pickAB(ab: A | B) {
      if ('a' in ab) {
        ab // Type is A
      } else {
        ab // Type is B
      }
      ab // Type is A | B
    }
    
  5. 内置函数, 比如 Array.isArray:

    function contains(text: string, terms: string|string[]) {
      const termList = Array.isArray(terms) ? terms : [terms];
      termList // Type is string[]
      // ...
    }
    
  6. tag标签:

    interface UploadEvent { type: 'upload'; filename: string; contents: string }
    interface DownloadEvent { type: 'download'; filename: string; }
    type AppEvent = UploadEvent | DownloadEvent;
    
    function handleEvent(e: AppEvent) {
    switch (e.type) {
        case 'download':
          e  // Type is DownloadEvent
          break;
        case 'upload':
          e;  // Type is UploadEvent
          break;
      }
    }
    

    这种模式被称为标签联合体

  7. 用户自定义的类型守护:

    function isInputElement(el: HTMLElement): el is HTMLInputElement {
      return 'value' in el;
    }
    
    function getElementContent(el: HTMLElement) {
      if (isInputElement(el)) {
        el; // Type is HTMLInputElement
        return el.value;
      }
      el; // Type is HTMLElement
      return el.textContent;
    }
    

    对于array, 类型守护依旧有用. 当我们对array进行搜索,可能会得到 undefine[]:

    const jackson5 = ['Jackie', 'Tito', 'Jermaine', 'Marlon', 'Michael'];
    const members = ['Janet', 'Michael'].map(
      who => jackson5.find(n => n === who)
    );  // Type is (string | undefined)[]
    

    ts缺无法执行用filter函数:

    Const members = ['Janet', 'Michael'].map(
      who => jackson5.find(n => n === who)
    ).filter(who => who !== undefined);  // Type is (string | undefined)[]
    

    那么可以用类型守护:

    function isDefined<T>(x: T | undefined): x is T {
      return x !== undefined;
    }
    const members = ['Janet', 'Michael'].map(
      who => jackson5.find(n 
    => n === who)
    ).filter(isDefined);  // Type is string[]
    

还有一些容易让人误解的类型缩小:

  1. typeof null
    const el = document.getElementById('foo'); // type is HTMLElement | null
    if (typeof el === 'object') {
      el;  // Type is HTMLElement | null
    }
    
    因为在js中, typeof null 是 'objec'
  2. !number 和 !string :
    function foo(x?: number|string|null) {
      if (!x) {
        x;  // Type is string | number | null | undefined
      }
    }
    
    空string 和0 都是falsy, 所以!x无法完全过滤string , number.