首页 > 解决方案 > 打字稿:根据自定义类型检查“typeof”

问题描述

我有一个自定义类型,比如说

export type Fruit = "apple" | "banana" | "grape";

我想确定一个字符串是否属于 Fruit 类型。我怎样才能做到这一点?

以下不起作用。

let myfruit = "pear";
if (typeof myfruit === "Fruit") {
    console.log("My fruit is of type 'Fruit'");
}

任何想法表示赞赏!

标签: typescripttypes

解决方案


简短的回答:

您不能typeof在运行时使用来检查interface仅在编译时存在的类型。相反,您可以编写用户定义的类型保护函数来检查此类类型:

const fruit = ["apple", "banana", "grape"] as const;
type Fruit = (typeof fruit)[number];
const isFruit = (x: any): x is Fruit => fruit.includes(x);

let myfruit = "pear";
if (isFruit(myfruit)) {
  console.log("My fruit is of type 'Fruit'");
}

长答案如下:


您可能会对 TypeScript 中值和类型之间的区别感到困惑,尤其是当它与typeof运算符相关时。您可能知道,TypeScript 为 JavaScript 添加了一个静态类型系统,并且当代码被转译时,该类型系统会被删除。TypeScript 的语法是这样的,一些表达式和语句引用运行时存在的,而其他表达式和语句引用仅在设计/编译时存在的类型。价值观类型,但它们本身不是类型。重要的是,在代码中的某些地方,编译器会期望一个值并将它找到的表达式解释为一个值,如果可能的话,编译器会期望一个类型并将它找到的表达式解释为一个类型。

经营者typeof过着双重生活。表达式typeof x总是期望x是一个值,但typeof x它本身可能是一个值或类型,具体取决于上下文:

let bar = {a: 0};
let TypeofBar = typeof bar; // the value "object"
type TypeofBar = typeof bar; // the type {a: number}

该行将let TypeofBar = typeof bar;通过 JavaScript,它将在运行时使用JavaScript typeof 运算符并生成一个字符串。但是type TypeofBar = typeof bar; 被删除,它使用TypeScript 类型查询运算符来检查 TypeScript 分配给名为 的值的静态类型bar

在您的代码中,

let myfruit = "pear";
if (typeof myfruit === "Fruit") { // "string" === "Fruit" ?!
    console.log("My fruit is of type 'Fruit'");
}

typeof myfruit是一个值,而不是一个类型。所以它是 JavaScripttypeof运算符,而不是 TypeScript 类型查询运算符。它总是返回值"string";它永远不会是Fruitor "Fruit"。您无法在运行时获得 TypeScript 类型查询运算符的结果,因为类型系统在运行时被擦除。你需要放弃typeof运营商。


可以做的是myfruit根据三个已知的Fruit字符串文字检查 的值......例如,这个:

let myfruit = "pear";
if (myfruit === "apple" || myfruit === "banana" || myfruit === "grape") {
  console.log("My fruit is of type 'Fruit'");
}

完美,对吧?好吧,也许这看起来像很多冗余代码。这是一种不太冗余的方法。首先,Fruit根据现有的文字值数组定义您的类型...... TypeScript 可以从值推断类型,但您不能从类型生成值。

const fruit = ["apple", "banana", "grape"] as const;
export type Fruit = (typeof fruit)[number];

您可以验证它Fruit与您手动定义的类型相同。然后,对于类型测试,您可以像这样使用用户定义的类型保护

const isFruit = (x: any): x is Fruit => fruit.includes(x);

isFruit()是一个函数,它检查它的参数是否在fruit数组中找到,如果是,则将其参数的类型缩小到Fruit. 让我们看看它的工作原理:

let myfruit = "pear";
if (isFruit(myfruit)) {
  console.log("My fruit is of type 'Fruit'");
}

该类型保护还让编译器知道在if语句的“then”子句中,myfruitFruit. 想象一下,如果您有一个只接受 的函数Fruit,以及一个可能是也可能不是 a 的值Fruit

declare function acceptFruit(f: Fruit): void;
const myfruit = Math.random() < 0.5 ? "pear" : "banana";

您不能直接调用该函数:

acceptFruit(myfruit); // error, myfruit might be "pear"

但是您可以在检查后在“then”子句中调用它:

if (isFruit(myfruit)) {
  acceptFruit(myfruit); // okay, myfruit is known to be "banana"
}

这大概就是您首先要检查自定义类型的原因。这样你就可以做到了。


回顾一下:你不能使用typeof. 您可以与字符串进行比较。您可以进行一些类型推断和类型保护,以消除重复代码并从编译器获取控制流类型分析。

Playground 代码链接


推荐阅读