Skip to content

三个经典的TypeScript易混淆点

前言

  • 本文会讲什么:主要讲解TypeScript在开发过程中的易混淆点,当然也同样是面试官常考的几个题目
  • 本文不会讲什么:本文并不是又大又全的TypeScript学习教程,不会讲那些基础知识、简单概念等,比如JS的内置类型这类。所以如果你是新手玩家,最好先去做一下新手任务出了新手村再这里

你知道interface与type有什么区别吗

官网这里有较为详细的介绍,并且提到一句类似于最佳实践的话:

For the most part, you can choose based on personal preference, and TypeScript will tell you if it needs something to be the other kind of declaration. If you would like a heuristic, use interface until you need to use features from type.

简单来说就是能用interface就用interface,除非Typescript提醒你了或者是interface根本实现不了这个功能。

具体来说它们有如下重要的区别

主要区别

type定义之后就不能重新添加新的属性;而interface则是始终可以扩展的;即仅inyterface支持合并类型

这里简单叙述一下官网中的例子:

ts
interface Window {
  title: string
}  
// 这步是OK的,`ts: TypeScriptAPI`就合并进入了之前定义的Window这个接口
interface Window {
  ts: TypeScriptAPI
}

const src = 'const a = "Hello World"';
window.ts.transpileModule(src, {});

而基于已经定义的type继续添加新的字段就会Error

ts
type Window = {
  title: string
}  
type Window = {
  ts: TypeScriptAPI
}  
 // Error: Duplicate identifier 'Window'.

其他区别:interface的限制

前面提到能使用interface的时候就使用interface,除了interface实现不了你想要的功能的时候。那本小节就描述一下interface又有什么限制:

不能直接操作基本类型(如stringnumber这些)

比如如下代码放在编译器中就会报错,因为extendsstring这个基本类型:

ts
interface X extends string {  // error
 // ...
}

type则可以,如下是大家可能经常使用的操作:

ts
type stringAlias = string;  // ok
type StringOrNumber = string | number;  // ok

本章小节

  • interface > type:合并类型
  • type > interface:操作基本类型

当然,基于已有知识如JavaScript进行联想,你可以简单理解为type == constinterface == class。这种理解也许有点片面,不过仅仅是为了方便记忆...

你知道never类型是用来干什么的吗

定义

故名思义,never是一种表示永远不会出现的类型,那什么是永远不会出现的类型呢,比如当一个函数陷入无限循环或者抛出异常时,我们就可以把这个函数的返回类型定义为never

如:

ts
function throwError(message: string): never {
  throw new Error(message);
}

注:never 类型仅能被赋值给另外一个 never

应用场景1

对于平常开发中,never相对来说可能是使用的较少的了。更多人可能只是知道其定义,但不知道其场景/作用。

第一个场景就是前面举例提到的定义无返回的函数的返回类型。当然,除了上述中抛出异常会导致函数无返回,还有种形式是产生了无限循环导致代码执行不到终点:

ts
function infiniteLoop(): never {
  while (true) {
    console.log("justin3go.com");
  }
}

我们可以思考一下:没有never时会导致什么坏情况出现

总的来说,never可以帮助 TypeScript 编译器更好地理解这个函数的行为,并在代码中进行类型检查。

例如,下面这个函数会抛出一个异常,表示输入的值不是一个有效的数字:

ts
function parseNumber(value: string): number {
  const result = Number(value);
  if (isNaN(result)) {
    throw new Error(`${value} is not a valid number.`);
  }
  return result;
}

如果我们尝试调用上述这个 parseNumber() ,但是传递了一个无效的字符串参数,TypeScript 编译器无法识别这个函数会抛出一个异常(此时是假设的没有never类型)。而此时它会将函数的返回类型设置为 number,这会导致一些类型检查错误。

比如一个大型系统中我们调用这个通用函数时,而仅仅看到了TS的提示说这个函数返回的是一个number,然后你就非常笃定其返回值是一个number,于是就基于这个number类型做了许多特别的操作,哦豁,后续很可能出现偶发性bug。

而当有了never类型,我们就可以设置为这样:

ts
function parseNumber(value: string): never | number {
  const result = Number(value);
  if (isNaN(result)) {
    throw new Error(`${value} is not a valid number.`);
  }
  return result;
}

现在,如果我们尝试调用 parseNumber 函数并传递一个无效的字符串参数,TypeScript 编译器会正确地推断出函数会抛出一个异常,并根据需要执行类型检查。这可以在我们编写更安全、更健壮的代码时提供非常好的帮助。

应用场景2

在 TypeScript 中,never 类型可以用作类型保护。因为如果一个变量的类型为 never,则可以确定该变量不可能有任何值。例如下方这个经典例子:

ts
function assertNever(x: never): never {
  throw new Error("Unexpected object: " + x);
}

function getValue(x: string | number): string {
  if (typeof x === "string") {
    return x;
  } else if (typeof x === "number") {
    return x.toString();
  } else {
    return assertNever(x);
  }
}

这里,我们在最后一个分支使用never类型做了兜底,如果不使用never,这里TS检查就可能报错,因为最后一个分支没有返回与函数返回值为string相互冲突:

void的区别

刚才那个例子其实我们这样避免报错(当然并不推荐,这里仅仅为了引入void):

  • 这样,就不会报错了,因为void表示该函数没有返回值,所以string | void1兼容了所有的分支情况,但是这里非常不推荐这么做,正确的做法还是assertNever那个例子
  • 原因是如果我们对这个函数按照参数类型正确传递参数,是不可能走到最后一个分支的,所以也就没必要单独在或一个void了,这样反而会误解这个函数的意思,增加操作;
  • 此时使用抛出异常这个never类型就可以既避免该函数的返回值检查,又可以做一个兜底,在后续确实传参错误的时候抛出异常以避免执行后续的代码

所以,那voidnever的区别是啥?

  • void:整个函数都正确执行完了,只是没有返回值
  • never:函数根本没有执行到返回的那一步

本章小节

never是一种表示永远不会出现的类型,主要在以下两种场景中使用:

  1. 无法执行到终点的函数的返回类型应设置为never
  2. 可以用作类型保护

其中,无法执行到终点 与 在终点不返回是两个意思。这也是nevervoid的主要区别。

你知道unknown和any之间的区别吗

概括

首先你可以将unknown理解为TS认可的一种类型,它确确实实是TS内置的一种类型;而any你可以理解为它是为了兼容JavaScript而出现的一种类型,与其说是兼容JavaScript,不如说是兼容那些不太会TypeScript的程序员。当然,有时候项目赶工确实很着急那也没办法...

unknown简述

unknown表示一种不确定的类型,即编译器无法确定该变量的类型,因此无法对该变量执行任何操作。通常情况下,unknown类型的变量需要进行类型检查或者类型断言后才能使用。例如:

ts
let userInput: unknown;
let userName: string;

userInput = 5;
userInput = 'hello';

// 需要进行类型检查
if (typeof userInput === 'string') {
  userName = userInput;
}

// 或者需要进行类型断言
userName = userInput as string;

any简述

any表示任意类型,即该变量可以是任何类型。使用any 类型会关闭 TypeScript 的类型检查,因此使用 "any" 类型时需要小心,因为它会导致代码中的类型错误难以被发现。例如:

ts
let userInput: any;
let userName: string;

userInput = 5;
userInput = 'hello';

// 没有类型检查
userName = userInput;

区别呢?

unknownany的最大区别是:

unknowntop type (任何类型都是它的 subtype) , 而 any 既是 top type, 又是 bottom type (它是任何类型的 subtype ) , 这导致 any 基本上就是放弃了任何类型检查。

因为any既是top type, 又是 bottom type,所以任何类型的值可以赋值给any,同时any类型的值也可以赋值给任何类型。但unknown 只是 top type,任何类型的值都可以赋值给它,但它只能赋值给unknownany,因为只有它俩是top type

上述话语原文:any和unknown

最后

本篇文章由于是几个经典的问题,所以我结合了chatgpt与其他的一些文章进行参考,如下:

其他两个问题也相继问了一下,有些帮助,但也仅此而已;这里疑惑的是我既然参考了它的回答,那我该不该引用它呢?如果引用它,那它的知识又来自于互联网,它自己却没有注明知识来源处...

最后最后,如本文有理解错误,欢迎各位友善指出。

参考