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

时间:2019-06-12 15:17:29

标签: typescript3.0

我正在尝试使用元素数组作为联合类型,这在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>更好的方法了?

3 个答案:

答案 0 :(得分:4)

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

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
}

答案 1 :(得分:1)

这里有一个解决方案,它适用于使用 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 发布。

答案 2 :(得分:0)

The standard library signature for Array<T>.includes(u)假定要检查的值与数组元素T的类型相同或更窄。但是,在您的情况下,您要做的是相反的操作,请检查 wide 类型的值。实际上,您唯一会说Array<T>.includes<U>(x: U)是一个错误并且必须禁止的情况是TU之间没有重叠(即,当T & Unever)。

现在,如果您不打算频繁使用includes()这种“相反”的使用,并且您希望运行时效果为零,则应将CAPITAL_LETTERS扩展为{{1 }}通过类型断言:

ReadonlyArray<string>

另一方面,如果您非常认真地认为应该在没有类型断言的情况下接受对(CAPITAL_LETTERS as ReadonlyArray<string>).includes(str); // okay 的使用,并且希望在所有代码中都使用它,则可以merge in a custom declaration

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; } } 的任何参数。参数类型。由于.includes()不是string & CapitalLetter,它将允许呼叫。不过,它仍将禁止never

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