【问题标题】:Typescript: Check "typeof" against custom type打字稿:根据自定义类型检查“typeof”
【发布时间】:2019-01-02 20:33:06
【问题描述】:

假设我有一个自定义类型

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

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

以下不起作用。

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

任何想法表示赞赏!

【问题讨论】:

    标签: typescript types


    【解决方案1】:

    简答:

    您不能在运行时使用typeof 来检查仅在编译时存在的interface 类型。相反,您可以编写 user-defined type guard function 来检查此类类型:

    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 添加了一个静态类型系统,that type system gets erased when the code is transpiled。 TypeScript 的语法是这样的:一些表达式和语句引用运行时存在的 values,而其他表达式和语句引用仅在设计/编译时存在的 types。值类型,但它们本身不是类型。重要的是,在代码中的某些地方,编译器会期望一个值并将它找到的表达式解释为一个值,如果可能的话,编译器会期望一个类型并将它找到的表达式解释为一个类型。

    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 operator 并生成一个字符串。但是type TypeofBar = typeof bar;被擦除,它正在使用 TypeScript type query operator 来检查 TypeScript 分配给名为 bar 的值的静态类型。

    在您的代码中,

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

    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'");
    }
    

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

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

    您可以验证Fruit 是否与您手动定义的类型相同。然后,对于类型测试,您可以像这样使用user-defined type guard

    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 的函数,以及一个可能是也可能不是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 link to code

    【讨论】:

    • 哇,很棒的答案,谢谢!我唯一需要改变的是const isFruit = (x: any): x is Fruit =&gt; fruit.includes(x);——我必须写fruit.indexOf(x) !== -1而不是fruit.includes(x)。否则,我收到以下错误:Property 'includes' does not exist on type ...
    • 这一定是我读过的最有趣和信息最丰富的 S/O 答案之一。谢谢!
    • 这确实是一个很棒的帖子。然而,我很难理解这条线的类型 Fruit = (typeof fruit)[number];正在工作
    • typeof fruitArray&lt;"apple" | "banana" | "grape"&gt;,所以Fruit 等价于(Array&lt;"apple" | "banana" | "grape"&gt;)[number]。语法T[K] 表示:T 的属性类型,其键为K 类型。所以(Array&lt;"apple" | "banana" | "grape"&gt;)[number] 表示“Array&lt;"apple" | "banana" | "grape"&gt; 的属性类型,其键类型为number”,或者:“Array&lt;"apple" | "banana" | "grape"&gt; 的数组元素,或者:"apple" | "banana" | "grape"
    • 惊人的解释。官方文档中有这样的内容吗?我对这条线特别惊讶:export type Fruit = (typeof fruit)[number]。怎么会有人这么想???
    【解决方案2】:

    TS 中的typeof

    TS 中的typeof 运算符可用于两种不同的上下文:

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

    示例:

    表达式/值上下文

    const hi = 'hi';
    const one = 1;
    const obj = {};
    
    console.log(typeof hi, typeof 1, typeof obj);
    // [LOG]: "string",  "number",  "object"

    输入上下文:

    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'
    

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

    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
        myfruit
    }
    

    【讨论】:

    • 是否可以从 'Fruit' 类型中获取字符串数组而不是硬编码?
    • @Alex.A,另一个答案涵盖了这个。
    猜你喜欢
    • 1970-01-01
    • 2018-11-03
    • 1970-01-01
    • 2021-08-20
    • 2020-03-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-08-29
    相关资源
    最近更新 更多