# 类型收窄

主要用于对一些类型做判断, 以把值的类型变成更具体的类型, 举个例子, 比如传入一个值,可能是数字或者字符串, 根据类型做不同的操作

function setWidth(width: number | string) {
  if (typeof width === 'number') {
    return width + 'px';
  }
  return width;
}

这里的 typeof 就把width的类型收窄为 number 或者 string中的其中一个, 然后就可以做对应的操作了。

TypeScript 已经实现了比如 if/else 、三元运算符、循环、真值检查等情况下的类型分析。TypeScript 会认为 typeof padding === number 是一种特殊形式的代码,我们称之为类型保护 (type guard),TypeScript 会沿着执行时可能的路径,分析值在给定的位置上最具体的类型。TypeScript 的类型检查器会考虑到这些类型保护和赋值语句,而这个将类型推导为更精确类型的过程,我们称之为收窄 (narrowing)

# typeof 类型保护(type guards)

JavaScript 本身就提供了 typeof 操作符,可以返回运行时一个值的基本类型信息,会返回如下这些特定的字符串:

  • "string"
  • "number"
  • "bigInt"
  • "boolean"
  • "symbol"
  • "undefined"
  • "object"
  • "function"

# 真值收窄(Truthiness narrowing)

其实也就是通过if判断值的真假, 来实现类型的具体化,举个例子

function printStr(strs: string | string[] | null) {
  if (strs && Array.isArray(strs)) {
    for (const s of strs) {
      console.log(s);
    }
  } else {
    console.log(strs);
  }
}

# 等值收窄(Equality narrowing)

function foo(x: number | string, y: string | boolean) {
  if (x === y) {
    console.log(x.toLowerCase());
  } else {
    console.log(x, y);
  }
}

比如上面的例子, 判断x和y完全相等, 但是他们的唯一共同类型只有string, 所以类型会被收窄为string。

当然判断更具体的值也会让类型收窄, 比如具体判断是不是等于null

function printStr(strs: string | string[] | null) {
  if (strs !== null) {
    if (Array.isArray(strs)) {
      for (const s of strs) {
        console.log(s);
      }
    } else {
      console.log(strs);
    }
  }
}

# in 操作符

const obj = { x: 1 };
if ('x' in obj) {
  console.log(obj.x);
}

也能做到类型收窄

# instanceOf

instanceof 也是一种类型保护,TypeScript 也可以通过识别 instanceof 正确的类型收窄:

# 赋值语句(Assignments)

其实就是自动类型推论, 是一个道理

// x 会首先被推断为 'hello' | 10
let x = Math.random() > 0.5 ? 'hello' : 10;
x = 1 // 当然重新赋值一个数字也是可以的
console.log(x.toFixed(2));
x = true // 重新赋值一个boolean是不允许的

# 类型判断式

class Fish {
  swim() {
    console.log('swim');
  }
}
class Bird {
  fly() {
    console.log('fly');
  }
}
function getPet(): Fish | Bird {
  return Math.random() > 0.5 ? new Fish() : new Bird();
}
const pet = getPet();
// 报错: 类型“Fish | Bird”上不存在属性“swim”。
/* if(pet.swim) {
  pet.swim()
} */
function isFish(pet: Fish | Bird): pet is Fish {
  return (pet as Fish).swim !== undefined;
}
if (isFish(pet)) {
  pet.swim();
} else {
  pet.fly();
}

其实我觉得有点麻烦 不如断言一下来的更快

# 穷尽检查

never 类型可以赋值给任何类型,然而,没有类型可以赋值给 never (除了 never 自身)。这就意味着你可以在 switch 语句中使用 never 来做一个穷尽检查。

enum ShapeKind {
  CIRCLE = 'circle',
  SQUARE = 'square'
}
interface Circle {
  kind: ShapeKind.CIRCLE;
  radius: number;
}
interface Square {
  kind: ShapeKind.SQUARE;
  sideLength: number;
}
type Shape = Circle | Square;
function getArea(shape: Shape) {
  switch (shape.kind) {
    case ShapeKind.CIRCLE:
      return Math.PI * shape.radius * shape.radius;
    case ShapeKind.SQUARE:
      return shape.sideLength * shape.sideLength;
    default:
      const _exhaustiveShape: never = shape;
      return _exhaustiveShape;
  }
}

这里我们采用switch穷尽检查kind属性, 这时候如果新增一个类型






 
 
 
 
 


enum ShapeKind {
  CIRCLE = 'circle',
  SQUARE = 'square',
  TRIANGLE = 'Triangle'
}
interface Triangle {
    kind: ShapeKind.TRIANGLE;
    bottomWidth: number;
    height: number;
}
type Shape = Circle | Square | Triangle;

因为 TypeScript 的收窄特性,执行到 default 的时候,类型被收窄为 Triangle,但因为任何类型都不能赋值给 never 类型,这就会产生一个编译错误。通过这种方式,你就可以确保 getArea 函数总是穷尽了所有 shape 的可能性。

上次更新: 1/22/2025, 9:39:13 AM