any & never & unknown

any(top type)

你可以在 any 类型变量上任意地进行操作,包括赋值、访问、方法调用等等,此时可以认为类型推导与检查是被完全禁用的。any 类型的主要意义,其实就是为了表示一个无拘无束的“任意类型”,它能兼容所有类型,也能够被所有类型兼容。

如何避免将 TypeScript 写成 AnyScript?

  • 如果是类型不兼容报错导致你使用 any,考虑用类型断言替代。
  • 如果是类型太复杂导致你不想全部声明而使用 any,考虑将这一处的类型去断言为你需要的最简类型。如你需要调用 foo.bar.baz(),就可以先将 foo 断言为一个具有 bar 方法的类型。
  • 如果你是想表达一个未知类型,更合理的方式是使用 unknown

unknown

unknown 类型和 any 类型有些类似,一个 unknown 类型的变量可以再次赋值为任意其它类型,但只能赋值给 any 与 unknown 类型的变量

简单地说,any 放弃了所有的类型检查,而 unknown 并没有。这一点也体现在对 unknown 类型的变量进行属性访问时需要进行类型断言,也就是虽然这是一个未知的类型,但我可以跟你保证它在这里就是这个类型。

never(bottom type)

type UnionWithNever = "aaa" | 599 | true | void | never;

将鼠标悬浮在类型别名之上,你会发现这里显示的类型是 "aaa" | 599 | true | void。never 类型被直接无视掉了,而 void 仍然存在。

这是因为,void 作为类型表示一个空类型,就像没有返回值的函数使用 void 来作为返回值类型标注一样,void 类型就像 JavaScript 中的 null 一样代表这里有类型,但是个空类型

而 never 才是一个什么都没有的类型,它甚至不包括空的类型,严格来说,never 类型不携带任何的类型信息,因此会在联合类型中被直接移除,比如我们看 void 和 never 的类型兼容性。

declare let v1: never;
declare let v2: void;

v1 = v2; // 报错 类型 void 不能赋值给类型 never
v2 = v1;

通常我们不会显式地声明一个 never 类型,它主要被类型检查所使用。但在某些情况下使用 never 确实是符合逻辑的,比如一个只负责抛出错误的函数。

never 使用一之抛出错误

function justThrow(): never {
  throw new Error()
}

在类型流的分析中,一旦一个返回值类型为 never 的函数被调用,那么下方的代码都会被视为无效的代码,无法执行到

function justThrow(): never {
  throw new Error()
}

function foo (input:number){
  if(input > 1){
    justThrow();
    // 下面的部分等同于 return 语句后的代码,即 Dead Code
    const name = "aaa";
  }
}

never 使用二之类型检测

我们也可以显式利用 never 来进行类型检查,这也就是上面在联合类型中 never 类型神秘消失的原因。假设,我们需要对一个联合类型的每个类型分支进行不同处理。

declare const strOrNumOrBool: string | number | boolean;

if (typeof strOrNumOrBool === "string") {
  console.log("str!");
} else if (typeof strOrNumOrBool === "number") {
  console.log("num!");
} else if (typeof strOrNumOrBool === "boolean") {
  console.log("bool!");
} else {
  throw new Error(`Unknown input type: ${strOrNumOrBool}`);
}

如果我们希望这个变量的每一种类型都需要得到妥善处理,在最后可以抛出一个错误,但这是运行时才会生效的措施,是否能在类型检查时就分析出来

实际上,由于 TypeScript 强大的类型分析能力,每经过一个 if 语句处理,strOrNumOrBool 的类型分支就会减少一个(因为已经被对应的 typeof 处理过)。而在最后的 else 代码块中,它的类型只剩下了 never 类型,即一个无法再细分、本质上并不存在的虚空类型。在这里,我们可以利用 never 类型变量仅能赋值给 never 类型变量的特性,来巧妙地分支处理检查:

if (typeof strOrNumOrBool === "string") {
  strOrNumOrBool.charAt(1);
} else if (typeof strOrNumOrBool === "number") {
  strOrNumOrBool.toFixed();
} else if (typeof strOrNumOrBool === "boolean") {
  strOrNumOrBool === true;
} else {
  // 如果出现了多余的类型,这里就会报一个类型错误
  const _exhaustiveCheck: never = strOrNumOrBool;
  throw new Error(`Unknown input type: ${_exhaustiveCheck}`);
}

never 的使用之三

never 还可以表示一个根本不存在的类型。

type StrAndNum = string & number; // never