【问题标题】:TypeScript const assertions: how to use Array.prototype.includes?TypeScript const 断言:如何使用 Array.prototype.includes?
【发布时间】:2019-10-27 03:56:41
【问题描述】:

我正在尝试使用元素数组作为联合类型,这在 TS 3.4 中使用 const 断言变得很容易,所以我可以这样做:

const CAPITAL_LETTERS = ['A', 'B', 'C', ..., 'Z'] as const;
type CapitalLetter = typeof CAPITAL_LETTERS[string];

现在我想测试一个字符串是否为大写字母,但以下失败并显示“不可分配给类型的参数”:

let str: string;
...
CAPITAL_LETTERS.includes(str);

有没有比将CAPITAL_LETTERS 转换为unknown 然后再转换为Array<string> 更好的方法来解决这个问题?

【问题讨论】:

  • 我想你想要type CapitalLetter = typeof CAPITAL_LETTERS[number]...也就是说,索引number,而不是string

标签: typescript3.0


【解决方案1】:

使用 lodash

const CAPITAL_LETTERS = ['A', 'B', 'C', 'Z'] as const;
_.includes(CAPITAL_LETTERS, 'A');

【讨论】:

  • 虽然这段代码 sn-p 可以解决问题,including an explanation 将帮助人们理解你的代码建议的原因。
【解决方案2】:

这是一个适用于字符串和字符串文字的解决方案,使用 TypeScript 4.1 Template Literal Types 不会破坏其他任何内容,并且在条件下使用时为了方便起见还缩小了类型:

declare global {
    interface ReadonlyArray<T> {
        includes<S, R extends `${Extract<S, string>}`>(
            this: ReadonlyArray<R>,
            searchElement: S,
            fromIndex?: number
        ): searchElement is R & S;
    }
}

最初由 noppa 在a TypeScript github issue related to this 发布。

【讨论】:

    【解决方案3】:

    另一种解决方法是使用类型保护

    https://www.typescriptlang.org/docs/handbook/advanced-types.html#user-defined-type-guards

    const myConstArray = ["foo", "bar", "baz"] as const
    
    function myFunc(x: string) {
        //Argument of type 'string' is not assignable to parameter of type '"foo" | "bar" | "baz"'.
        if (myConstArray.includes(x)) {
            //Hey, a string could totally be one of those values! What gives, TS?
        }
    }
    
    //get the string union type
    type TMyConstArrayValue = typeof myConstArray[number]
    
    //Make a type guard
    //Here the "x is TMyConstArrayValue" tells TS that if this fn returns true then x is of that type
    function isInMyConstArray(x: string): x is TMyConstArrayValue {
        return myConstArray.includes(x as TMyConstArrayValue)
    
        //Note the cast here, we're doing something TS things is unsafe but being explicit about it
        //I like to this of type guards as saying to TS:
        //"I promise that if this fn returns true then the variable is of the following type"
    }
    
    function myFunc2(x: string) {
        if (isInMyConstArray(x)) {
            //x is now "foo" | "bar" | "baz" as originally intended!
        }
    }
    

    虽然您必须引入另一个“不必要的”功能,但最终看起来干净且工作完美。在你的情况下,你会添加

    const CAPITAL_LETTERS = ['A', 'B', 'C', ..., 'Z'] as const;
    type CapitalLetter = typeof CAPITAL_LETTERS[string];
    function isCapitalLetter(x: string): x is CapitalLetter {
        return CAPITAL_LETTERS.includes(x as CapitalLetter)
    }
    
    let str: string;
    isCapitalLetter(str) //Now you have your comparison
    
    //Not any more verbose than writing .includes inline
    if(isCapitalLetter(str)){
      //now str is of type CapitalLetter
    }
    

    【讨论】:

    • 这很酷!我不知道类型保护功能。
    【解决方案4】:

    The standard library signature for Array&lt;T&gt;.includes(u) 假定要检查的值与数组元素T 的类型相同或更窄。但在您的情况下,您正在做相反的事情,检查一个 wider 类型的值。事实上,只有在TU 之间没有重叠时(即T &amp; Unever),你才会说Array&lt;T&gt;.includes&lt;U&gt;(x: U) 是一个错误并且必须被禁止。

    现在,如果您不打算经常对includes() 进行这种“相反”的使用,并且希望运行时效应为零,那么您应该通过类型断言将CAPITAL_LETTERS 扩大到ReadonlyArray&lt;string&gt;

    (CAPITAL_LETTERS as ReadonlyArray<string>).includes(str); // okay
    

    另一方面,如果您认为includes() 的这种使用应该在没有类型断言的情况下被接受,并且您希望它在您的所有代码中发生,您可以merge in a custom declaration

    // global augmentation needed if your code is in a module
    // if your code is not in a module, get rid of "declare global":
    declare global { 
      interface ReadonlyArray<T> {
        includes<U>(x: U & ((T & U) extends never ? never : unknown)): boolean;
      }
    }
    

    这将使数组(嗯,一个只读数组,但这就是您在此示例中所拥有的)将允许.includes() 的任何参数,只要数组元素类型和参数之间存在一些重叠类型。由于string &amp; CapitalLetter 不是never,它会允许调用。不过,它仍然会禁止CAPITAL_LETTERS.includes(123)

    好的,希望对您有所帮助;祝你好运!

    【讨论】:

    • 标准定义不允许测试更广泛的类型是否有特定原因?使用 const 断言数组测试更广泛的类型不是 .includes 的全部意义吗?如果我已经知道我的值是只读数组的成员(更窄的类型),那么我不需要调用包含这使得签名原样无用不是吗?
    • 我认为如果数组包含文字,那么是的,禁止更广泛的类型似乎很疯狂。但是在像stringnumber 这样的情况下,有大量可能的值,那么这是合理的。我认为这不是一个预期的用例,并且允许它的增强更复杂并且可能有一些边缘情况。使用包含检查时,通常很容易扩展数组类型。 ?‍♂️
    猜你喜欢
    • 2022-12-09
    • 2020-03-30
    • 2019-08-09
    • 2020-07-05
    • 1970-01-01
    • 2022-11-24
    • 1970-01-01
    • 2018-12-17
    • 2019-08-21
    相关资源
    最近更新 更多