TypeScript const 断言:如何使用 Array.prototype.includes?

dol*_*s3m 21 typescript3.0

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

const CAPITAL_LETTERS = ['A', 'B', 'C', ..., 'Z'] as const;
type CapitalLetter = typeof CAPITAL_LETTERS[string];
Run Code Online (Sandbox Code Playgroud)

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

let str: string;
...
CAPITAL_LETTERS.includes(str);
Run Code Online (Sandbox Code Playgroud)

有没有更好的方法来解决这个问题,而不是投射CAPITAL_LETTERSunknown然后到Array<string>

jca*_*alz 21

的标准库签名Array<T>.includes(u)假定要检查的值与数组元素的类型相同或更窄T。但是在您的情况下,您正在做相反的事情,检查更广泛类型的值。事实上,唯一一次你会说这Array<T>.includes<U>(x: U)是一个错误并且必须被禁止的时候是如果T和之间没有重叠U(即,当T & Unever)。

现在,如果您不打算includes()经常使用这种“相反”的用法,并且想要零运行时影响,则应该扩大CAPITAL_LETTERSReadonlyArray<string>via 类型断言:

(CAPITAL_LETTERS as ReadonlyArray<string>).includes(str); // okay
Run Code Online (Sandbox Code Playgroud)

另一方面,如果您认为includes()应该接受这种使用而没有类型断言,并且您希望它发生在您的所有代码中,那么您可以在自定义声明中合并

// 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;
  }
}
Run Code Online (Sandbox Code Playgroud)

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

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

  • 标准定义不允许测试更广泛的类型是否有具体原因?使用常量断言数组测试更广泛的类型不是 .includes 的全部意义吗?如果我已经知道我的值是只读数组(较窄类型)的成员,那么我不需要调用包含,这使得签名毫无用处,不是吗? (8认同)
  • 我使用了 `x: (T &amp; U) 从未延伸?never : U` 代替,这看起来更简单、更直接。 (2认同)

ima*_*gio 6

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

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!
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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
}
Run Code Online (Sandbox Code Playgroud)


Aze*_*rum 6

添加到@imagio的答案,您可以制作一个通用类型保护(感谢@wprl的简化)

function isIn<T>(values: readonly T[], x: any): x is T {
    return values.includes(x);
}
Run Code Online (Sandbox Code Playgroud)

并将其与任何as const数组一起使用:

const specialNumbers = [0, 1, 2, 3] as const;

function foo(n: number) {
     if (isIn(specialNumbers, n)) {
         //TypeScript will say that `s` has type `0 | 1 | 2 | 3` here
     }
}
Run Code Online (Sandbox Code Playgroud)

  • 谢谢你。也许可以简化为“function isIn&lt;T&gt;(values: readonly T[], x: T): x is T { return value.includes(x) }” (2认同)