《Effective TypeScript》条款22 - 类型收缩

本文主要记录书中关于TypeScript类型收缩的内容

本文主要内容如下

  1. 类型收缩的一些方法
    1. 条件判断
    2. 抛错误
    3. instanceof 和 in 属性检查
    4. “标签联合”或“可辨识联合”
  2. 类型收缩的失效示例
  3. 自定义类型保护
  4. 总结

 

类型收缩的方法

条件判断

const el = document.getElementById("foo");
if (el) {
    el; // 类型是HTMLElement
} else {
    el; // 类型是 null
}

如果 el 是 null,那么第一个分支的代码就不会执行,所以 TypeScript 能够在这个代码块中推断出一个更容易处理的类型。

抛错误

const el = document.getElementById("foo");
if(!el) throw new Error('未找到元素');
el.innerHTML = 'Hello World'; //HTMLElement

instanceof 和 in 属性检查

function contains (text:string,search:string|RegExp) {
    if(search instanceof RegExp) {
        // 类型是Regexp
        return !!search.test(text);
    }

    // 类型是 string
    return text.includes(search); 
}

 

interface A {a:number}
interface A {a:number}
interface B {b:number}

function pickAB(ab:A|B) {
    if('a' in ab) {
        ab // 类型是A
    } else {
        ab // 类型是B
    }

    ab // 类型是A|B
}

 

ps:原文中提到一个示例如下:

function contains(text:string,terms:string|string[]) {
    const termList = Array.isArray(terms) ? terms : [terms];
    termList // 类型是 string[]
}

这是通过内置函数来完成类型收缩的

“标签联合”或“可辨识联合”

interface UploadEvent {type:'upload',filename:string;content:string}
interface DownloadEvent {type:'download',filename:string;}
type AppEvent = UploadEvent|DownloadEvent;

function handleEvent(e:AppEvent) {
    switch(e.type) {
        case "upload":
            e // 类型是UploadEvent
            break;
        case 'download':
            e // 类型是Download
            break;
    }
}

帮助检查器收缩范围的常见方法是给他们加上一个明确的“标签”,这种模式称为 “标签联合” 或 “可辨识联合”,它在TypeScript中很常见。

类型收缩的失效示例

const el = document.querySelector('#foo');
if(typeof el === 'object') {
    el; // Element | null
}

在 JavaScript 中,typeof null 也是 "object",所以这个类型收缩是不成立的,类似的还有如下这样:

function foo(x?:number|string|null) {
    if(!x) {
        x // string | number | null | undefined
    }
}

 

自定义类型保护

 
function isInputElement(el:HTMLElement): el is HTMLInputElement {
    return 'value' in el;
}


function getElementContent(el:HTMLElement) {
    if(isInputElement(el)) {
        el // 类型是 HTMLInputElement;
        return el.value;
    }
    return el.textContent; // 类型是 HTMLElement
}

这就是所谓的“自定义类型保护”,el is HTMLInputElement 作为返回值,如果函数返回true,它可以收缩类型的参数。

跨数组或对象的类型收缩

const students = ["小明", "小红", "小柔"];

// const people = ["小红", "小柔"].map((who) => students.find((n) => n === who)); //类型 (string | undefined)[]

// 如果用filter过滤undefined,TypeScript 无法跟上filter的逻辑

// const people = ["小红", "小柔"]
//     .map((who) => students.find((n) => n === who))
//     .filter((who) => who != undefined);
    // 类型仍然是 (string | undefined)[]

// 但是使用类型保护就可以

function isDefined(x:T|undefined):x is T {
    return x != undefined;
}


const people = ["小红", "小柔"]
    .map((who) => students.find((n) => n === who))
    .filter(isDefined);
    // 类型是 string[]

总结

  • 了解TypeScript如何根据条件和其他类型的控制流来收缩类型。
  • 使用标记/可辨识类型或者自定义类型保护来收缩类型。

你可能感兴趣的:(《Effective TypeScript》条款22 - 类型收缩)