关于TS组合类型的检查,你应该知道的内容

1,065 阅读3分钟

TS 的类型是可以组合使用的 其实就是 TS 联合

窄化和类型守卫

function getValue(x: number | string, y: string) {
  return new Array(x + 1).join(" ") + y; // 这个代码在ts中是会报错的  运算符“+”不能应用于类型“string | number”和“number”。ts(2365)
}

// 如果我们加上判断

function getValue(x: number | string, y: string) {
  if (typeof x === "number") {
    return new Array(x + 1).join(" ") + y; // 这个时候是不会报错的
  }
  return x + y;
}

当我们使用了if+typeof操作后,ts 可以识别变窄后的类型,成为窄化 (Narrowing) 这样可以让 ts 知道 x 当前是什么类型

在实现层面,TS 会认为 typeof x === “number” 这样的表达式是一种类型守卫 (type guard) 表达式 ,本质上就是 if + type guard∑ 实现了窄化(Narrowing)

总结:类型窄化 就是根据类型守卫在子语句块中重新定义了更加具体的类型

真值窄化

function returnValue(
  values: number[] | undefined,
  i: number
): number[] | undefined {
  // 这个地方其实就是真值窄化
  if (!values) {
    return values;
  } else {
    return values.map((x) => x * i);
  }
}

真值窄化 可以帮助我们更好的应对 null/undefined/0 等值带来的问题

相等性窄化

相等性窄化 === !== == != 都可以用来窄化类型

看个例子

function printType(x:string | string[] | null){
    if(x !== null){
        if(typeof x === 'object){
            for(const s of x){
                // x的类型就是string[]
            }
        }else if(typeof x === 'string'){
             // x的类型就是string
        }
    }
}

in 操作符窄化

js 中的 in 操作符 是用来检验对象是否存在某个属性

type Fish = { swim: () => void };
type Bird = { fly: () => void };

function move(animal: Fish | Bird) {
  if ("swim" in animal) {
    return animal.swim();
  }
  return animal.fly();
}

这个地方没有 instanceof 是因为 type 定义的东西 没有运行时 Fish 只存在在编译的时候

instanceof 窄化

function getData(x: Date | string) {
  if (x instanceof Date) {
    // x 是 Date类型
  } else {
    // x 是string 类型
  }
}

这里要注意 Date 是真实的 Function 类型 而不能是 type 定义的

控制流分析

ts 是怎么做到窄化的功能的?

首先在语法分析阶段,ts 的编译器会识别出来类型卫兵表达式,包括一些隐性的类型卫兵,比如真值表达式 instanceof

在语义分析的时候,ts 遇到控制流关键字 if/while等 就会看这里有没有需要窄化的操作

  • 首先 ts 会看到一个表达式 例如 typeof x === 'number'
  • 然后 ts 会对返回值 做窄化
  • 窄化的本质就是重新定义类型

很多语句都会触发窄化

类型断言

ts 有两个断言的操作符号 as 和 is

as 操作符是提示 ts 某种类型是什么
is 操作符是用户自定的类型守卫

判别的联合

interface Circle {
  kind: "circle";
  radius: number;
}

interface Square {
  kind: "square";
  sideLength: number;
}

type Shape = Circle | Square;

function getArea(shape: Shape) {
  switch (shape.kind) {
    case "circle":
      return Math.PI * shape.radius ** 2;
    case "square":
      return shape.sideLength ** 2;
  }
}
// switch case 在帮助我们做类型的窄化

Never 类型

never 就是不应该出现的值 当我们不希望代码走到这个地方 可以使用 never

窄化解决了什么问题

联合类型在使用中根据不同控制条件重定义 提升了对联合类型的校验