打字稿:根据自定义类型检查"typeof"

mai*_*aia 18 types typescript

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

export type Fruit = "apple" | "banana" | "grape";
Run Code Online (Sandbox Code Playgroud)

我想确定一个字符串是否是Fruit类型的一部分.我怎么能做到这一点?

以下不起作用.

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

任何想法赞赏!

jca*_*alz 48

您可能会对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}
Run Code Online (Sandbox Code Playgroud)

该行将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'");
}
Run Code Online (Sandbox Code Playgroud)

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


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

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

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

const stringLitArray = <L extends string>(arr: L[]) => arr;
const fruit = stringLitArray(["apple", "banana", "grape"]);
export type Fruit = (typeof fruit)[number];
Run Code Online (Sandbox Code Playgroud)

您可以验证该Fruit手动与您自己定义的类型相同.然后,对于类型测试,您可以使用这样的用户定义类型保护:

const isFruit = (x: any): x is Fruit => fruit.includes(x);
Run Code Online (Sandbox Code Playgroud)

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

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

那个类型的守护程序也让编译器知道在if语句的"then"子句中,myfruit就是a Fruit.想象一下,如果你有一个只接受的函数,Fruit一个值可能是也可能不是Fruit:

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

您不能直接调用该函数:

acceptFruit(myfruit); // error, myfruit might be "pear"
Run Code Online (Sandbox Code Playgroud)

但是你可以在检查之后在"then"子句中调用它:

if (isFruit(myfruit)) {
  acceptFruit(myfruit); // okay, myfruit is known to be "banana"
}
Run Code Online (Sandbox Code Playgroud)

这可能是您想要首先检查自定义类型的原因.这样你就可以做到.


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

希望有所帮助.祝好运.

  • 这确实是一个很棒的帖子。然而,我很难理解这一行 type Fruit = (typeoffruit)[number]; 是如何实现的。工作中 (10认同)
  • `typeoffruit` 是 `Array&lt;"apple" | “香蕉”| "grape"&gt;`,因此 `Fruit` 相当于 `(Array&lt;"apple" | "banana" | "grape"&gt;)[number]`。语法“T[K]”的意思是:键为“K”类型的“T”属性的类型。因此 `(Array&lt;"apple" | "banana" | "grape"&gt;)[number]` 表示“`Array&lt;"apple" | "banana" | "grape"&gt;` 的属性类型,其键为输入`number`”,或者:“`Array&lt;“apple”|“banana”|“grape”&gt;`的数组元素,或者:`“apple”|“banana”|“grape”`。 (10认同)
  • 哇,很棒的答案,谢谢!我唯一需要改变的是`const isFruit = (x: any): x is Fruit =&gt;fruit.includes(x);` - 而不是`fruit.includes(x)`,我不得不写`fruit.includes(x)`。 indexOf(x) !== -1`。否则,我收到以下错误:`Property 'includes' does not exist on type ...` (3认同)
  • 这一定是我读过的最有趣、信息最丰富的 S/O 答案之一。谢谢! (3认同)
  • 惊人的解释。官方文档中有类似的内容吗?我对这一行感到特别惊讶:````export type Fruit = (typeoffruit)[number]````。怎么会有人想出这个办法??? (2认同)

Wil*_*een 9

typeof在 TS 中:

TS 中的运算typeof符可以在 2 种不同的上下文中使用:

  1. 在表达式/值上下文中返回其类型的字符串。这只是 JavaScripttypeof运算符,编译后将保留。
  2. 在类型上下文中使类型类似于现有的表达式/值。这是一个 TS 构造,可以帮助我们更轻松地使用某些类型表达自己。这将被编译掉并且不会出现在编译后的 JavaScript 中

例子:

表达式/值上下文

const hi = 'hi';
const one = 1;
const obj = {};

console.log(typeof hi, typeof 1, typeof obj);
// [LOG]: "string",  "number",  "object"
Run Code Online (Sandbox Code Playgroud)

输入上下文:

const obj1 = {foo: 1, bar: true};
const obj2 = {foo: 1, bar: ''};

// test has the type according to the structure of obj1
const test: typeof obj1 = {foo: 1, bar: true};
// typeof obj1 is the same as:
type sameAsTypeofObj1 = {foo: number, bar: string}


// test2 has the type according to the structure of obj1
const test2: typeof obj2 = {foo: 1, bar: true};
// In test2 we get a compile error since bar is not correct
// Since the type of obj2 is {foo: number, bar: string} we get the error:
// Type 'boolean' is not assignable to type 'string'
Run Code Online (Sandbox Code Playgroud)

对于您的具体问题,我认为您应该使用用户定义的类型防护。这是一个例子:

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

let myfruit = "apple";

// user defined type guard
function isFruit(fruit: string): fruit is Fruit {
  return ["apple", "banana", "grape"].indexOf(fruit) !== -1;
}

if (isFruit(myfruit)) {
    // if this condition passes 
    // then TS compiler knows that myfruit is of the Fruit type
    console.log(`${myfruit} is a Fruit.`}
}
Run Code Online (Sandbox Code Playgroud)

  • 是否可以从“Fruit”类型获取字符串数组而不是对其进行硬编码? (2认同)
  • 我不明白为什么我们必须编写一个“isFruit”方法,而它可以从类型本身推导出来,使用像“is(Fruit)”这样的运算符。这太冗长了,而且不干! (2认同)